├── .github ├── actions │ ├── build_linux │ │ ├── Dockerfile │ │ └── entrypoint.sh │ ├── build_osx │ │ ├── Dockerfile │ │ └── entrypoint.sh │ ├── build_win │ │ ├── Dockerfile │ │ └── entrypoint.sh │ ├── combine_zip │ │ └── script.sh │ └── upload_zip │ │ └── script.sh └── workflows │ ├── buildDevelop.yml │ └── buildRelease.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── manual └── ModularFungi.jpg ├── plugin.json ├── res ├── Blank_10HP.png ├── Blank_12HP.png ├── Blank_16HP.png ├── Blank_1HP.png ├── Blank_20HP.png ├── Blank_26HP.png ├── Blank_32HP.png ├── Blank_3HP.png ├── Blank_4HP.png ├── Blank_6HP.png ├── Colors.png ├── LightsOff.png ├── Zen_10HP.png ├── Zen_12HP.png ├── Zen_16HP.png ├── Zen_1HP.png ├── Zen_20HP.png ├── Zen_26HP.png ├── Zen_32HP.png ├── Zen_3HP.png ├── Zen_4HP.png ├── Zen_6HP.png ├── scope2mmPort.svg ├── scopeTinyKnob.svg └── scopeTinyPort.svg └── src ├── Bitmap.cpp ├── Bitmap.hpp ├── Blanks.cpp ├── Colors.cpp ├── LightsOff.cpp ├── ModularFungi.cpp ├── ModularFungi.hpp └── Scope.cpp /.github/actions/build_linux/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | LABEL "com.github.actions.name"="VCVRackPluginBuilder-Linux" 4 | LABEL "com.github.actions.description"="Builds a VCV Rack plugin for Linux" 5 | LABEL "com.github.actions.icon"="headphones" 6 | LABEL "com.github.actions.color"="purple" 7 | 8 | LABEL "repository"="TBD" 9 | LABEL "homepage"="TBD" 10 | LABEL "maintainer"="dewb" 11 | 12 | RUN apt-get update 13 | RUN apt-get install -y build-essential cmake curl gcc g++ git make tar unzip zip libgl1-mesa-dev libglu1-mesa-dev jq 14 | 15 | ADD entrypoint.sh /entrypoint.sh 16 | RUN chmod a+x /entrypoint.sh 17 | 18 | ENTRYPOINT ["/entrypoint.sh"] 19 | -------------------------------------------------------------------------------- /.github/actions/build_linux/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | export RACK_DIR=${GITHUB_WORKSPACE}/Rack-SDK 6 | export RACK_USER_DIR=${GITHUB_WORKSPACE} 7 | 8 | git submodule update --init --recursive 9 | 10 | curl -L https://vcvrack.com/downloads/Rack-SDK-${RACK_SDK_VERSION}.zip -o rack-sdk.zip 11 | unzip -o rack-sdk.zip 12 | rm rack-sdk.zip 13 | 14 | make clean 15 | make dist 16 | -------------------------------------------------------------------------------- /.github/actions/build_osx/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM debian 3 | 4 | LABEL "com.github.actions.name"="VCVRackPluginBuilder-OSX" 5 | LABEL "com.github.actions.description"="Builds a VCV Rack plugin for OS X" 6 | LABEL "com.github.actions.icon"="headphones" 7 | LABEL "com.github.actions.color"="purple" 8 | 9 | LABEL "repository"="TBD" 10 | LABEL "homepage"="TBD" 11 | LABEL "maintainer"="dewb" 12 | 13 | RUN apt-get update && \ 14 | apt-get upgrade -yy && \ 15 | apt-get install -yy \ 16 | automake \ 17 | bison \ 18 | curl \ 19 | file \ 20 | flex \ 21 | git \ 22 | libtool \ 23 | pkg-config \ 24 | python \ 25 | texinfo \ 26 | vim \ 27 | wget \ 28 | zlib1g-dev \ 29 | build-essential \ 30 | cmake \ 31 | make \ 32 | tar \ 33 | unzip \ 34 | zip \ 35 | libgl1-mesa-dev \ 36 | libglu1-mesa-dev \ 37 | jq \ 38 | rsync 39 | 40 | # Install osxcross 41 | # NOTE: The Docker Hub's build machines run varying types of CPUs, so an image 42 | # built with `-march=native` on one of those may not run on every machine - I 43 | # ran into this problem when the images wouldn't run on my 2013-era Macbook 44 | # Pro. As such, we remove this flag entirely. 45 | ENV OSXCROSS_SDK_VERSION 10.11 46 | RUN SDK_VERSION=$OSXCROSS_SDK_VERSION \ 47 | mkdir /opt/osxcross && \ 48 | cd /opt && \ 49 | git clone https://github.com/tpoechtrager/osxcross.git && \ 50 | cd osxcross && \ 51 | git checkout e0a171828a72a0d7ad4409489033536590008ebf && \ 52 | sed -i -e 's|-march=native||g' ./build_clang.sh ./wrapper/build.sh && \ 53 | ./tools/get_dependencies.sh && \ 54 | curl -L -o ./tarballs/MacOSX${OSXCROSS_SDK_VERSION}.sdk.tar.xz \ 55 | https://github.com/apriorit/osxcross-sdks/raw/master/MacOSX${OSXCROSS_SDK_VERSION}.sdk.tar.xz && \ 56 | yes | PORTABLE=true ./build.sh && \ 57 | ./build_compiler_rt.sh 58 | 59 | ENV PATH $PATH:/opt/osxcross/target/bin 60 | 61 | ADD entrypoint.sh /entrypoint.sh 62 | RUN chmod a+x /entrypoint.sh 63 | 64 | ENTRYPOINT ["/entrypoint.sh"] 65 | -------------------------------------------------------------------------------- /.github/actions/build_osx/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | export RACK_DIR=${GITHUB_WORKSPACE}/Rack-SDK 6 | export RACK_USER_DIR=${GITHUB_WORKSPACE} 7 | 8 | export CC=x86_64-apple-darwin15-clang 9 | export CXX=x86_64-apple-darwin15-clang++ 10 | export STRIP=x86_64-apple-darwin15-strip 11 | 12 | git submodule update --init --recursive 13 | 14 | curl -L https://vcvrack.com/downloads/Rack-SDK-${RACK_SDK_VERSION}.zip -o rack-sdk.zip 15 | unzip -o rack-sdk.zip 16 | rm rack-sdk.zip 17 | 18 | make clean 19 | make dist 20 | -------------------------------------------------------------------------------- /.github/actions/build_win/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:stretch 2 | 3 | LABEL "com.github.actions.name"="VCVRackPluginBuilder-Windows" 4 | LABEL "com.github.actions.description"="Builds a VCV Rack plugin for Windows" 5 | LABEL "com.github.actions.icon"="headphones" 6 | LABEL "com.github.actions.color"="purple" 7 | 8 | LABEL "repository"="TBD" 9 | LABEL "homepage"="TBD" 10 | LABEL "maintainer"="dewb" 11 | 12 | RUN apt-get update 13 | RUN apt-get install -y build-essential cmake curl gcc g++ git make tar unzip zip libgl1-mesa-dev libglu1-mesa-dev jq g++-mingw-w64-x86-64 14 | 15 | ADD entrypoint.sh /entrypoint.sh 16 | RUN chmod a+x /entrypoint.sh 17 | 18 | ENTRYPOINT ["/entrypoint.sh"] 19 | -------------------------------------------------------------------------------- /.github/actions/build_win/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | export RACK_DIR=${GITHUB_WORKSPACE}/Rack-SDK 6 | export RACK_USER_DIR=${GITHUB_WORKSPACE} 7 | 8 | export CC=x86_64-w64-mingw32-gcc-posix 9 | export CXX=x86_64-w64-mingw32-g++-posix 10 | export STRIP=x86_64-w64-mingw32-strip 11 | 12 | git submodule update --init --recursive 13 | 14 | curl -L https://vcvrack.com/downloads/Rack-SDK-${RACK_SDK_VERSION}.zip -o rack-sdk.zip 15 | unzip -o rack-sdk.zip 16 | rm rack-sdk.zip 17 | 18 | make clean 19 | make dist 20 | -------------------------------------------------------------------------------- /.github/actions/combine_zip/script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | GITHUB_API_URL=https://api.github.com 6 | 7 | GITHUB_TOKEN=$1 8 | 9 | curl -o release.json \ 10 | --header "Authorization: token ${GITHUB_TOKEN}" \ 11 | --request GET \ 12 | ${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/${GITHUB_REF#"refs/"} 13 | 14 | UPLOAD_URL=$(jq -r .upload_url release.json) 15 | 16 | DOWNLOAD_LIST=$(jq -r ".assets | .[] | .url" release.json) 17 | 18 | rm release.json 19 | 20 | mkdir dist 21 | 22 | echo "$DOWNLOAD_LIST" | while IFS= read -r line 23 | do 24 | curl -L -o $(basename $line).zip --header "Authorization: token ${GITHUB_TOKEN}" --header "Accept: application/octet-stream" --request GET $line 25 | unzip -o $(basename $line).zip -d dist 26 | rm $(basename $line).zip 27 | done 28 | 29 | PLUGIN_BUILD_SLUG=$(jq -r .slug plugin.json) 30 | PLUGIN_BUILD_NAME=${PLUGIN_BUILD_SLUG}-$(jq -r .version plugin.json) 31 | cd dist 32 | zip -q -9 -r ${PLUGIN_BUILD_NAME}.zip ./${PLUGIN_BUILD_SLUG} 33 | cd .. 34 | 35 | ASSET_PATH=$(ls dist/*.zip) 36 | 37 | curl -i \ 38 | --header "Authorization: token ${GITHUB_TOKEN}" \ 39 | --header "Content-Type: application/zip" \ 40 | --request POST \ 41 | --data-binary @"${ASSET_PATH}" \ 42 | ${UPLOAD_URL%"{?name,label\}"}?name=${ASSET_PATH#"dist/"} 43 | -------------------------------------------------------------------------------- /.github/actions/upload_zip/script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | GITHUB_API_URL=https://api.github.com 6 | 7 | GITHUB_TOKEN=$1 8 | 9 | # Get release url 10 | curl -o release.json \ 11 | --header "Authorization: token ${GITHUB_TOKEN}" \ 12 | --request GET \ 13 | ${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/${GITHUB_REF#"refs/"} 14 | 15 | UPLOAD_URL=$(jq -r .upload_url release.json) 16 | 17 | ASSET_PATH=$(ls dist/*.zip) 18 | 19 | curl -i \ 20 | --header "Authorization: token ${GITHUB_TOKEN}" \ 21 | --header "Content-Type: application/zip" \ 22 | --request POST \ 23 | --data-binary @"${ASSET_PATH}" \ 24 | ${UPLOAD_URL%"{?name,label\}"}?name=${ASSET_PATH#"dist/"} 25 | -------------------------------------------------------------------------------- /.github/workflows/buildDevelop.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - develop 5 | name: Develop 6 | env: 7 | RACK_SDK_VERSION: 1.1.6 8 | jobs: 9 | buildLinux: 10 | name: Build Linux 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@master 14 | - name: Build Linux 15 | uses: ./.github/actions/build_linux 16 | -------------------------------------------------------------------------------- /.github/workflows/buildRelease.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [published] 4 | name: Release 5 | env: 6 | RACK_SDK_VERSION: 1.1.6 7 | jobs: 8 | buildLinux: 9 | name: Build Linux 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@master 13 | - name: Build Linux 14 | uses: ./.github/actions/build_linux 15 | - name: upload zip 16 | run: sh ./.github/actions/upload_zip/script.sh ${{ secrets.GITHUB_TOKEN }} 17 | buildWindows: 18 | name: Build Windows 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@master 22 | - name: Build Windows 23 | uses: ./.github/actions/build_win 24 | - name: upload zip 25 | run: sh ./.github/actions/upload_zip/script.sh ${{ secrets.GITHUB_TOKEN }} 26 | buildOsx: 27 | name: Build OSX 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@master 31 | - name: Build OSX 32 | uses: ./.github/actions/build_osx 33 | - name: upload zip 34 | run: sh ./.github/actions/upload_zip/script.sh ${{ secrets.GITHUB_TOKEN }} 35 | combineDist: 36 | name: Combine Distributions 37 | runs-on: ubuntu-latest 38 | needs: [buildLinux, buildWindows, buildOsx] 39 | steps: 40 | - uses: actions/checkout@master 41 | - name: combine zip 42 | run: sh ./.github/actions/combine_zip/script.sh ${{ secrets.GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /dist 3 | /plugin.dylib 4 | /plugin.dll 5 | /plugin.so 6 | .DS_Store 7 | /WK_Custom.tunings 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, David O'Rourke 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Must follow the format in the Naming section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html 2 | 3 | # Must follow the format in the Versioning section of https://vcvrack.com/manual/PluginDevelopmentTutorial.html 4 | 5 | # FLAGS will be passed to both the C and C++ compiler 6 | FLAGS += 7 | CFLAGS += 8 | CXXFLAGS += 9 | 10 | # Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path. 11 | # Static libraries are fine. 12 | LDFLAGS += 13 | 14 | # Add .cpp and .c files to the build 15 | SOURCES += $(wildcard src/*.cpp) 16 | 17 | # Add files to the ZIP package when running `make dist` 18 | # The compiled plugin is automatically added. 19 | DISTRIBUTABLES += $(wildcard LICENSE*) res 20 | 21 | # If RACK_DIR is not defined when calling the Makefile, default to two levels above 22 | RACK_DIR ?= ../.. 23 | include $(RACK_DIR)/arch.mk 24 | 25 | ifdef ARCH_WIN 26 | LDFLAGS += -lopengl32 27 | endif 28 | 29 | # Include the VCV Rack plugin Makefile framework 30 | include $(RACK_DIR)/plugin.mk 31 | 32 | # Make resources 33 | 34 | RESOURCES += $(subst src/res/,res/,$(wildcard src/res/*.svg)) 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modular Fungi 2 | 3 | ![](https://img.shields.io/github/workflow/status/david-c14/ModularFungi/Release?label=Release) ![](https://img.shields.io/github/v/release/david-c14/ModularFungi?label=Latest) ![](https://img.shields.io/github/release-date/david-c14/ModularFungi?label=Released) 4 | 5 | ### Omri has posted the following message:- ### 6 | 7 | So we decided, for now, not to update the Modular Fungi collection to V2. I’m not 100% happy with the blanks, and there’s no need anymore for the Light Off module so all there’s left is the Opsylloscope. It’s quite a cool scope but we would like to improve it sometime in the future, maybe even add other modules and change the blanks designs so, for now, there’s no need to port the collection. I know that it’s not a big collection, and probably will not cause any issues if not ported, but it’s important for me that it will not be ported for now. Thanks! 8 | 9 | ---- 10 | 11 | ![](manual/ModularFungi.jpg) 12 | 13 | 10 Blanking plates in various sizes. 14 | 15 | [Downloads](https://github.com/david-c14/ModularFungi/releases) 16 | 17 | #### The Modular Fungi collection was designed by [Omri Cohen](https://bit.ly/2P2watb) 18 | and coded by [David O'Rourke](https://github.com/david-c14) and [Ben](https://github.com/stoermelder) 19 | 20 | The Modular Fungi collection is available for free, and any [donations](https://paypal.me/omricohencomposer) 21 | that would encourage more creation are welcome. 22 | 23 | 24 | -------------------------------------------------------------------------------- /manual/ModularFungi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/manual/ModularFungi.jpg -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "ModularFungi", 3 | "name": "Modular Fungi", 4 | "author": "Omri Cohen", 5 | "license": "BSD-3-Clause", 6 | "authorUrl": "https://www.youtube.com/channel/UCuWKHSHTHMV_nVSeNH4gYAg", 7 | "pluginUrl": "https://vcvrack.submarine.org.uk/ModularFungi", 8 | "sourceUrl": "https://github.com/david-c14/ModularFungi", 9 | "donateUrl": "https://paypal.me/omricohencomposer", 10 | "manualUrl": "https://github.com/david-c14/ModularFungi/blob/master/README.md", 11 | "version": "1.1.4", 12 | "modules": [ 13 | { 14 | "slug":"Blank1HP", 15 | "name":"Blank 1HP", 16 | "description":"1 HP Blanking Plate", 17 | "tags":["Blank"] 18 | }, 19 | { 20 | "slug":"Blank3HP", 21 | "name":"Blank 3HP", 22 | "description":"3 HP Blanking Plate", 23 | "tags":["Blank"] 24 | }, 25 | { 26 | "slug":"Blank4HP", 27 | "name":"Blank 4HP", 28 | "description":"4 HP Blanking Plate", 29 | "tags":["Blank"] 30 | }, 31 | { 32 | "slug":"Blank6HP", 33 | "name":"Blank 6HP", 34 | "description":"6 HP Blanking Plate", 35 | "tags":["Blank"] 36 | }, 37 | { 38 | "slug":"Blank10HP", 39 | "name":"Blank 10HP", 40 | "description":"10 HP Blanking Plate", 41 | "tags":["Blank"] 42 | }, 43 | { 44 | "slug":"Blank12HP", 45 | "name":"Blank 12HP", 46 | "description":"12 HP Blanking Plate", 47 | "tags":["Blank"] 48 | }, 49 | { 50 | "slug":"Blank16HP", 51 | "name":"Blank 16HP", 52 | "description":"16 HP Blanking Plate", 53 | "tags":["Blank"] 54 | }, 55 | { 56 | "slug":"Blank20HP", 57 | "name":"Blank 20HP", 58 | "description":"20 HP Blanking Plate", 59 | "tags":["Blank"] 60 | }, 61 | { 62 | "slug":"Blank26HP", 63 | "name":"Blank 26HP", 64 | "description":"26 HP Blanking Plate", 65 | "tags":["Blank"] 66 | }, 67 | { 68 | "slug":"Blank32HP", 69 | "name":"Blank 32HP", 70 | "description":"32 HP Blanking Plate", 71 | "tags":["Blank"] 72 | }, 73 | { 74 | "slug":"Color12HP", 75 | "name":"Color Scheme", 76 | "description":"12 HP Blanking Plate with Color Key", 77 | "tags":["Blank"] 78 | }, 79 | { 80 | "slug":"LightsOff", 81 | "name":"Lights Off", 82 | "description":"Dim the lights in your Rack!", 83 | "tags":["Utility"] 84 | }, 85 | { 86 | "slug":"Opsylloscope", 87 | "name":"Opsylloscope", 88 | "description":"Scope for when the lights are off", 89 | "tags":["Utility"] 90 | } 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /res/Blank_10HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Blank_10HP.png -------------------------------------------------------------------------------- /res/Blank_12HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Blank_12HP.png -------------------------------------------------------------------------------- /res/Blank_16HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Blank_16HP.png -------------------------------------------------------------------------------- /res/Blank_1HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Blank_1HP.png -------------------------------------------------------------------------------- /res/Blank_20HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Blank_20HP.png -------------------------------------------------------------------------------- /res/Blank_26HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Blank_26HP.png -------------------------------------------------------------------------------- /res/Blank_32HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Blank_32HP.png -------------------------------------------------------------------------------- /res/Blank_3HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Blank_3HP.png -------------------------------------------------------------------------------- /res/Blank_4HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Blank_4HP.png -------------------------------------------------------------------------------- /res/Blank_6HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Blank_6HP.png -------------------------------------------------------------------------------- /res/Colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Colors.png -------------------------------------------------------------------------------- /res/LightsOff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/LightsOff.png -------------------------------------------------------------------------------- /res/Zen_10HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Zen_10HP.png -------------------------------------------------------------------------------- /res/Zen_12HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Zen_12HP.png -------------------------------------------------------------------------------- /res/Zen_16HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Zen_16HP.png -------------------------------------------------------------------------------- /res/Zen_1HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Zen_1HP.png -------------------------------------------------------------------------------- /res/Zen_20HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Zen_20HP.png -------------------------------------------------------------------------------- /res/Zen_26HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Zen_26HP.png -------------------------------------------------------------------------------- /res/Zen_32HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Zen_32HP.png -------------------------------------------------------------------------------- /res/Zen_3HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Zen_3HP.png -------------------------------------------------------------------------------- /res/Zen_4HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Zen_4HP.png -------------------------------------------------------------------------------- /res/Zen_6HP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/david-c14/ModularFungi/d70c7dd8e73ac385ee6199c2e42b3c6b3b5da9a2/res/Zen_6HP.png -------------------------------------------------------------------------------- /res/scope2mmPort.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 57 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /res/scopeTinyKnob.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 42 | 44 | 45 | 47 | image/svg+xml 48 | 50 | 51 | 52 | 53 | 54 | 58 | 62 | 67 | 72 | 77 | 82 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /res/scopeTinyPort.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 21 | 27 | 28 | 30 | 34 | 35 | 37 | 41 | 48 | 49 | 50 | 57 | 62 | 63 | 65 | 71 | 72 | 74 | 78 | 79 | 81 | 87 | 88 | 90 | 94 | 95 | 97 | 101 | 108 | 109 | 110 | 117 | 122 | 123 | 125 | 131 | 132 | 134 | 138 | 139 | 140 | 163 | 165 | 166 | 168 | image/svg+xml 169 | 171 | 172 | 173 | 174 | 175 | 179 | 183 | 188 | 193 | 198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /src/Bitmap.cpp: -------------------------------------------------------------------------------- 1 | #include "Bitmap.hpp" 2 | 3 | void MFTexture::reload(NVGcontext *vg, std::string fileName, int imageFlags) { 4 | if (image) 5 | nvgDeleteImage(vg, image); 6 | image = nvgCreateImage(vg, fileName.c_str(), imageFlags); 7 | name = fileName; 8 | context = vg; 9 | refCount++; 10 | if (!image) 11 | return; 12 | nvgImageSize(vg, image, &width, &height); 13 | } 14 | void MFTexture::release() { 15 | refCount--; 16 | if (refCount) 17 | return; 18 | refCount = 0; 19 | if (image) 20 | nvgDeleteImage(context, image); 21 | image = 0; 22 | } 23 | 24 | std::shared_ptr MFTextureList::load(NVGcontext *vg, std::string fileName, int imageFlags) { 25 | for (std::shared_ptr tex : list) { 26 | if ((tex->context == vg) && !tex->name.compare(fileName)) { 27 | if (tex->image) { 28 | tex->refCount++; 29 | return tex; 30 | } 31 | tex->reload(vg, fileName, imageFlags); 32 | return tex; 33 | } 34 | } 35 | std::shared_ptr tex = std::make_shared(vg, fileName, imageFlags); 36 | list.push_back(tex); 37 | return tex; 38 | } 39 | 40 | MFTextureList gTextureList; 41 | 42 | void BitMap::DrawImage(NVGcontext *vg) { 43 | if (!loaded) { 44 | loaded = true; 45 | bitmap = gTextureList.load(vg, path, 0); 46 | if (!bitmap->image) 47 | WARN("ModularFungi: Unable to load %s", path.c_str()); 48 | } 49 | if (!bitmap->image) 50 | return; 51 | NVGpaint paint = nvgImagePattern(vg, 0, 0, box.size.x, box.size.y, 0.0f, bitmap->image, 1.0f); 52 | nvgFillPaint(vg, paint); 53 | nvgBeginPath(vg); 54 | nvgRect(vg, 0, 0, box.size.x, box.size.y); 55 | nvgFill(vg); 56 | 57 | } 58 | void BitMap::draw(const DrawArgs &args) { 59 | DrawImage(args.vg); 60 | TransparentWidget::draw(args); 61 | } 62 | -------------------------------------------------------------------------------- /src/Bitmap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "rack.hpp" 4 | 5 | using namespace rack; 6 | 7 | struct MFTexture { 8 | int image = 0; 9 | std::string name; 10 | NVGcontext *context; 11 | int width; 12 | int height; 13 | int refCount = 0; 14 | MFTexture(NVGcontext *vg, std::string fileName, int imageFlags) { 15 | reload(vg, fileName, imageFlags); 16 | } 17 | void reload(NVGcontext *vg, std::string fileName, int imageFlags); 18 | void release(); 19 | ~MFTexture() { 20 | release(); 21 | } 22 | }; 23 | 24 | struct MFTextureList { 25 | std::vector> list; 26 | std::shared_ptr load(NVGcontext *vg, std::string fileName, int imageFlags); 27 | }; 28 | 29 | extern MFTextureList gTextureList; 30 | 31 | struct BitMap : TransparentWidget { 32 | std::string path; 33 | int loaded = false; 34 | std::shared_ptr bitmap; 35 | void DrawImage(NVGcontext *vg); 36 | void draw(const DrawArgs &args) override; 37 | ~BitMap() { 38 | if (bitmap) 39 | bitmap->release(); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/Blanks.cpp: -------------------------------------------------------------------------------- 1 | #include "ModularFungi.hpp" 2 | 3 | struct BlankBaseWidget : ModuleWidget { 4 | static constexpr int LISTSIZE = 2; 5 | int selected = 0; 6 | std::string fileName[LISTSIZE]; 7 | BitMap *bmp; 8 | std::string FileName(std::string tpl, int templateSize) { 9 | char workingSpace[100]; 10 | snprintf(workingSpace, 100, tpl.c_str(), templateSize); 11 | return asset::plugin(pluginInstance, workingSpace); 12 | } 13 | 14 | BlankBaseWidget(Module *module) : ModuleWidget() { 15 | setModule(module); 16 | } 17 | void appendContextMenu(Menu *menu) override; 18 | void loadBitmap() { 19 | bmp = createWidget(Vec(0,0)); 20 | bmp->box.size.x = box.size.x; 21 | bmp->box.size.y = box.size.y; 22 | bmp->path = fileName[selected]; 23 | addChild(bmp); 24 | } 25 | void setBitmap(int sel) { 26 | if (selected == sel) 27 | return; 28 | selected = clamp(sel, 0, LISTSIZE - 1); 29 | removeChild(bmp); 30 | delete bmp; 31 | loadBitmap(); 32 | } 33 | json_t *toJson() override { 34 | json_t *rootJ = ModuleWidget::toJson(); 35 | json_object_set_new(rootJ, "style", json_real(selected)); 36 | return rootJ; 37 | } 38 | void fromJson(json_t *rootJ) override { 39 | ModuleWidget::fromJson(rootJ); 40 | int sel = selected; 41 | json_t *styleJ = json_object_get(rootJ, "style"); 42 | if (styleJ) 43 | sel = json_number_value(styleJ); 44 | setBitmap(sel); 45 | } 46 | 47 | }; 48 | 49 | struct BitmapMenuItem : MenuItem { 50 | BlankBaseWidget *w; 51 | int value; 52 | void onAction(const event::Action &e) override { 53 | w->setBitmap(value); 54 | } 55 | }; 56 | 57 | void BlankBaseWidget::appendContextMenu(Menu *menu) { 58 | menu->addChild(new MenuEntry); 59 | BitmapMenuItem *m = createMenuItem("Classic"); 60 | m->w = this; 61 | m->value = 0; 62 | m->rightText = CHECKMARK(selected==m->value); 63 | menu->addChild(m); 64 | m = createMenuItem("Zen"); 65 | m->w = this; 66 | m->value = 1; 67 | m->rightText = CHECKMARK(selected==m->value); 68 | menu->addChild(m); 69 | } 70 | 71 | template 72 | struct BlankWidget : BlankBaseWidget { 73 | BlankWidget(Module *module) : BlankBaseWidget(module) { 74 | fileName[0] = FileName("res/Blank_%dHP.png", x); 75 | fileName[1] = FileName("res/Zen_%dHP.png", x); 76 | box.size = Vec(RACK_GRID_WIDTH * x, RACK_GRID_HEIGHT); 77 | loadBitmap(); 78 | } 79 | }; 80 | 81 | Model *modelBlank_1HP = createModel>("Blank1HP"); 82 | Model *modelBlank_3HP = createModel>("Blank3HP"); 83 | Model *modelBlank_4HP = createModel>("Blank4HP"); 84 | Model *modelBlank_6HP = createModel>("Blank6HP"); 85 | Model *modelBlank_10HP = createModel>("Blank10HP"); 86 | Model *modelBlank_12HP = createModel>("Blank12HP"); 87 | Model *modelBlank_16HP = createModel>("Blank16HP"); 88 | Model *modelBlank_20HP = createModel>("Blank20HP"); 89 | Model *modelBlank_26HP = createModel>("Blank26HP"); 90 | Model *modelBlank_32HP = createModel>("Blank32HP"); 91 | -------------------------------------------------------------------------------- /src/Colors.cpp: -------------------------------------------------------------------------------- 1 | #include "ModularFungi.hpp" 2 | 3 | struct ColorWidget : ModuleWidget { 4 | BitMap *bmp; 5 | 6 | ColorWidget(Module *module) : ModuleWidget() { 7 | setModule(module); 8 | box.size = Vec(RACK_GRID_WIDTH * 12, RACK_GRID_HEIGHT); 9 | loadBitmap(); 10 | } 11 | void loadBitmap() { 12 | bmp = createWidget(Vec(0,0)); 13 | bmp->box.size.x = box.size.x; 14 | bmp->box.size.y = box.size.y; 15 | bmp->path = asset::plugin(pluginInstance, "res/Colors.png"); 16 | addChild(bmp); 17 | } 18 | }; 19 | 20 | Model *modelColor_12HP = createModel("Color12HP"); 21 | -------------------------------------------------------------------------------- /src/LightsOff.cpp: -------------------------------------------------------------------------------- 1 | // "Lights Off" module for VCV Rack 2 | // 3 | // BSD 3-Clause License 4 | // 5 | // Copyright (C) 2020 Benjamin Dill 6 | // All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions are met: 10 | // 11 | // * Redistributions of source code must retain the above copyright notice, this 12 | // list of conditions and the following disclaimer. 13 | // 14 | // * Redistributions in binary form must reproduce the above copyright notice, 15 | // this list of conditions and the following disclaimer in the documentation 16 | // and/or other materials provided with the distribution. 17 | // 18 | // * Neither the name of the copyright holder nor the names of its 19 | // contributors may be used to endorse or promote products derived from 20 | // this software without specific prior written permission. 21 | // 22 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 26 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 30 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | 33 | 34 | #include "ModularFungi.hpp" 35 | 36 | 37 | struct LightsOffModule : Module { 38 | enum ParamIds { 39 | PARAM_DIM, 40 | NUM_PARAMS 41 | }; 42 | enum InputIds { 43 | NUM_INPUTS 44 | }; 45 | enum OutputIds { 46 | NUM_OUTPUTS 47 | }; 48 | enum LightIds { 49 | LIGHT_ENABLED, 50 | NUM_LIGHTS 51 | }; 52 | 53 | bool active = false; 54 | 55 | LightsOffModule() { 56 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 57 | configParam(PARAM_DIM, 0.0f, 1.0f, 0.8f, "Dim", "%", 0.f, 100.f); 58 | } 59 | 60 | bool isActive() { 61 | return active && !bypass; 62 | } 63 | }; 64 | 65 | static LightsOffModule *lightsOffSingleton = NULL; 66 | 67 | 68 | struct LightsOffContainer : widget::Widget { 69 | LightsOffModule *module; 70 | 71 | void draw(const DrawArgs& args) override { 72 | if (module && module->isActive()) { 73 | // Dim layer 74 | box = parent->box.zeroPos(); 75 | nvgBeginPath(args.vg); 76 | nvgRect(args.vg, 0, 0, box.size.x, box.size.y); 77 | nvgFillColor(args.vg, nvgRGBA(0x00, 0x00, 0x00, (char)(255.f * module->params[LightsOffModule::PARAM_DIM].getValue()))); 78 | nvgFill(args.vg); 79 | 80 | // Draw lights 81 | Rect viewPort = getViewport(box); 82 | std::queue q; 83 | q.push(APP->scene->rack->moduleContainer); 84 | while (!q.empty()) { 85 | Widget* w = q.front(); 86 | q.pop(); 87 | 88 | LightWidget *lw = dynamic_cast(w); 89 | if (lw) { 90 | Vec p1 = lw->getRelativeOffset(Vec(), this); 91 | Vec p = getAbsoluteOffset(Vec()).neg(); 92 | p = p.plus(p1); 93 | p = p.div(APP->scene->rackScroll->zoomWidget->zoom); 94 | 95 | // Draw only if currently visible 96 | if (viewPort.isIntersecting(Rect(p, lw->box.size))) { 97 | nvgSave(args.vg); 98 | nvgResetScissor(args.vg); 99 | nvgTranslate(args.vg, p.x, p.y); 100 | lw->draw(args); 101 | nvgRestore(args.vg); 102 | } 103 | } 104 | 105 | for (Widget *w1 : w->children) { 106 | q.push(w1); 107 | } 108 | } 109 | 110 | // Draw cable plugs 111 | for (widget::Widget *w : APP->scene->rack->cableContainer->children) { 112 | CableWidget *cw = dynamic_cast(w); 113 | assert(cw); 114 | cw->drawPlugs(args); 115 | } 116 | } 117 | Widget::draw(args); 118 | } 119 | 120 | void onHoverKey(const event::HoverKey &e) override { 121 | const char* keyName = glfwGetKeyName(e.key, 0); 122 | if (e.action == GLFW_PRESS && keyName && *keyName == 'x' && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_ALT)) { 123 | module->active ^= true; 124 | } 125 | Widget::onHoverKey(e); 126 | } 127 | }; 128 | 129 | 130 | struct DimParamWidget : ParamWidget { 131 | void onButton(const event::Button& e) override { 132 | // Touch parameter 133 | if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT && (e.mods & RACK_MOD_MASK) == 0) { 134 | if (paramQuantity) { 135 | APP->scene->rack->touchedParam = this; 136 | } 137 | } 138 | } 139 | 140 | void onHoverScroll(const event::HoverScroll& e) override { 141 | if (e.scrollDelta.y > 0.f) { 142 | paramQuantity->moveScaledValue(0.1f); 143 | } 144 | else { 145 | paramQuantity->moveScaledValue(-0.1f); 146 | } 147 | e.consume(this); 148 | } 149 | 150 | void onEnter(const event::Enter& e) override { } 151 | void onLeave(const event::Leave& e) override { } 152 | }; 153 | 154 | struct LightsOffWidget : ModuleWidget { 155 | BitMap *bmp; 156 | LightsOffContainer *loContainer; 157 | bool enabled = false; 158 | 159 | LightsOffWidget(LightsOffModule *module) { 160 | setModule(module); 161 | box.size = Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 162 | 163 | bmp = createWidget(Vec(0,0)); 164 | bmp->box.size.x = box.size.x; 165 | bmp->box.size.y = box.size.y; 166 | bmp->path = FileName("res/LightsOff.png", 1); 167 | addChild(bmp); 168 | 169 | DimParamWidget *dimParamWidget = createParam(Vec(0, 0), module, LightsOffModule::PARAM_DIM); 170 | dimParamWidget->box.size.x = box.size.x; 171 | dimParamWidget->box.size.y = box.size.y; 172 | addParam(dimParamWidget); 173 | 174 | addChild(createLightCentered>(Vec(7.5f, 38.0f), module, LightsOffModule::LIGHT_ENABLED)); 175 | 176 | if (module && !lightsOffSingleton) { 177 | enabled = true; 178 | lightsOffSingleton = module; 179 | if (enabled) { 180 | loContainer = new LightsOffContainer; 181 | loContainer->module = module; 182 | // This is where the magic happens: add a new widget on top-level to Rack 183 | APP->scene->rack->addChild(loContainer); 184 | } 185 | } 186 | } 187 | 188 | ~LightsOffWidget() { 189 | if (enabled && loContainer) { 190 | lightsOffSingleton = NULL; 191 | APP->scene->rack->removeChild(loContainer); 192 | delete loContainer; 193 | } 194 | } 195 | 196 | std::string FileName(std::string tpl, int templateSize) { 197 | char workingSpace[100]; 198 | snprintf(workingSpace, 100, tpl.c_str(), templateSize); 199 | return asset::plugin(pluginInstance, workingSpace); 200 | } 201 | 202 | void step() override { 203 | if (module) { 204 | module->lights[LightsOffModule::LIGHT_ENABLED].setBrightness(enabled); 205 | } 206 | ModuleWidget::step(); 207 | } 208 | 209 | void appendContextMenu(Menu *menu) override { 210 | LightsOffModule *module = dynamic_cast(this->module); 211 | 212 | struct ActiveItem : MenuItem { 213 | LightsOffModule *module; 214 | void onAction(const event::Action &e) override { 215 | module->active ^= true; 216 | } 217 | void step() override { 218 | rightText = module->active ? "✔" : ""; 219 | MenuItem::step(); 220 | } 221 | }; 222 | 223 | struct DimSlider : ui::Slider { 224 | DimSlider(LightsOffModule *module) { 225 | box.size.x = 180.0f; 226 | quantity = module->paramQuantities[LightsOffModule::PARAM_DIM]; 227 | } 228 | }; 229 | 230 | menu->addChild(new MenuSeparator()); 231 | menu->addChild(construct(&MenuLabel::text, "Hotkey " RACK_MOD_CTRL_NAME "+Alt+X")); 232 | menu->addChild(construct(&MenuItem::text, "Active", &ActiveItem::module, module)); 233 | menu->addChild(new DimSlider(module)); 234 | } 235 | }; 236 | 237 | Model *modelLightsOff = createModel("LightsOff"); 238 | -------------------------------------------------------------------------------- /src/ModularFungi.cpp: -------------------------------------------------------------------------------- 1 | #include "ModularFungi.hpp" 2 | 3 | 4 | Plugin *pluginInstance; 5 | 6 | 7 | void init(rack::Plugin *p) { 8 | pluginInstance = p; 9 | 10 | // Add all Models defined throughout the pluginInstance 11 | p->addModel(modelBlank_1HP); 12 | p->addModel(modelBlank_3HP); 13 | p->addModel(modelBlank_4HP); 14 | p->addModel(modelBlank_6HP); 15 | p->addModel(modelBlank_10HP); 16 | p->addModel(modelBlank_12HP); 17 | p->addModel(modelBlank_16HP); 18 | p->addModel(modelBlank_20HP); 19 | p->addModel(modelBlank_26HP); 20 | p->addModel(modelBlank_32HP); 21 | p->addModel(modelColor_12HP); 22 | p->addModel(modelLightsOff); 23 | p->addModel(modelOpsylloscope); 24 | 25 | // Any other pluginInstance initialization may go here. 26 | // As an alternative, consider lazy-loading assets and lookup tables when your module is created to reduce startup times of Rack. 27 | } 28 | -------------------------------------------------------------------------------- /src/ModularFungi.hpp: -------------------------------------------------------------------------------- 1 | #include "Bitmap.hpp" 2 | 3 | using namespace rack; 4 | 5 | // Forward-declare the Plugin, defined in Template.cpp 6 | extern Plugin *pluginInstance; 7 | 8 | // Forward-declare each Model, defined in each module source file 9 | extern Model *modelBlank_1HP; 10 | extern Model *modelBlank_3HP; 11 | extern Model *modelBlank_4HP; 12 | extern Model *modelBlank_6HP; 13 | extern Model *modelBlank_10HP; 14 | extern Model *modelBlank_12HP; 15 | extern Model *modelBlank_16HP; 16 | extern Model *modelBlank_20HP; 17 | extern Model *modelBlank_26HP; 18 | extern Model *modelBlank_32HP; 19 | extern Model *modelColor_12HP; 20 | extern Model *modelLightsOff; 21 | extern Model *modelOpsylloscope; 22 | //extern Model *modelME; 23 | 24 | -------------------------------------------------------------------------------- /src/Scope.cpp: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2018 Andrew Belt and licensed under BSD-3-Clause by Andrew Belt 2 | 3 | //Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | // software and associated documentation files (the "Software"), to deal in the Software 5 | // without restriction, including without limitation the rights to use, copy, modify, 6 | // merge, publish, distribute, sublicense, and/or sell copies of the Software, 7 | // and to permit persons to whom the Software is furnished to do so, subject to the 8 | // following conditions: 9 | 10 | //The above copyright notice and this permission notice shall be included in all copies 11 | // or substantial portions of the Software. 12 | 13 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 14 | // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 15 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 16 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 18 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | 21 | //Modified by Dave French 2020 22 | //Added use with lights off module, kaleidoscope plots, resizable, line types, line widths & fading lines 23 | //reworked UI, moved options to context menu. 24 | 25 | //Popout window code by Richie Hindle 26 | 27 | #include 28 | #include 29 | #include 30 | #include "ModularFungi.hpp" 31 | 32 | // Get the GLFW API. 33 | #define GLEW_STATIC 34 | 35 | #include 36 | #include 37 | 38 | 39 | ///512 in original scope, 4096 with variable bufferSize 40 | // This improves the drawing resolution, but reduces performance 41 | // The size of the buffer use is chooses by the performance options 42 | // in the context menu 43 | static const auto MAX_BUFFER_SIZE = 4096; 44 | 45 | struct Scope : Module { 46 | enum ParamIds { 47 | X_SCALE_PARAM, 48 | X_POS_PARAM, 49 | Y_SCALE_PARAM, 50 | Y_POS_PARAM, 51 | TIME_PARAM, 52 | LISSAJOUS_PARAM, //unused, kept for 1.1.compatibility 53 | TRIG_PARAM, 54 | EXTERNAL_PARAM, 55 | KALEIDOSCOPE_USE_PARAM, //unused, kept for 1.1.compatibility 56 | KALEIDOSCOPE_COUNT_PARAM, 57 | KALEIDOSCOPE_RADIUS_PARAM, 58 | KALEIDOSCOPE_COLOR_SPREAD_PARAM, 59 | LINE_WIDTH_PARAM, 60 | LINE_FADE_PARAM, 61 | LINE_HUE_PARAM, 62 | LINE_TYPE_PARAM, 63 | SHOW_STATS_PARAM, 64 | SHOW_LABELS_PARAM, 65 | PLOT_TYPE_PARAM, 66 | EXT_WINDOW_ALPHA_PARAM, 67 | NUM_PARAMS 68 | }; 69 | enum InputIds { 70 | X_INPUT, 71 | Y_INPUT, 72 | TRIG_INPUT, 73 | HUE_INPUT, 74 | LINE_WIDTH_INPUT, 75 | KALEIDOSCOPE_COUNT_INPUT, 76 | KALEIDOSCOPE_RADIUS_INPUT, 77 | KALEIDOSCOPE_COLOR_SPREAD_INPUT, 78 | X_SCALE_INPUT, 79 | Y_SCALE_INPUT, 80 | X_POS_INPUT, 81 | Y_POS_INPUT, 82 | TIME_INPUT, 83 | TRIG_LEVEL_INPUT, 84 | LINE_TYPE_INPUT, 85 | PLOT_TYPE_INPUT, 86 | NUM_INPUTS 87 | }; 88 | enum OutputIds { 89 | NUM_OUTPUTS 90 | }; 91 | enum LightIds { 92 | NUM_LIGHTS 93 | }; 94 | 95 | enum LineType { 96 | NORMAL_LINE, 97 | VECTOR_LINE, 98 | EXPERIMENTAL_LINE, 99 | NUM_LINES 100 | }; 101 | 102 | enum PlotType { 103 | NORMAL, 104 | LISSAJOUS, 105 | KALEIDOSCOPE, 106 | NUM_PLOT_TYPES 107 | }; 108 | 109 | float bufferX[PORT_MAX_CHANNELS][MAX_BUFFER_SIZE] = {}; 110 | float bufferY[PORT_MAX_CHANNELS][MAX_BUFFER_SIZE] = {}; 111 | int channelsX = 0; 112 | int channelsY = 0; 113 | int bufferIndex = 0; 114 | int frameIndex = 0; 115 | int bufferSize = 512; 116 | 117 | //parameters for kaleidoscope 118 | struct Kaleidoscope { 119 | int count = 3; 120 | float radius = 20.0f; 121 | } kaleidoscope; 122 | 123 | dsp::SchmittTrigger triggers[16]; 124 | 125 | // used to calculate parameter + cv values 126 | float hue = 0.5f; 127 | float lineWidth = 1.5f; 128 | float fade = 1.0f; 129 | std::atomic widgetWidth; 130 | 131 | Scope() { 132 | widgetWidth.store(RACK_GRID_WIDTH * 20); 133 | 134 | const auto timeBase = (float) MAX_BUFFER_SIZE / 6; 135 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 136 | configParam(X_SCALE_PARAM, -2.f, 8.f, 0.f, "X scale", " ", 1 / 2.f, 5); 137 | configParam(X_POS_PARAM, -10.f, 10.f, 0.f, "X position", " "); 138 | configParam(Y_SCALE_PARAM, -2.f, 8.f, 0.f, "Y scale", " ", 1 / 2.f, 5); 139 | configParam(Y_POS_PARAM, -10.f, 10.f, 0.f, "Y position", " "); 140 | configParam(TIME_PARAM, 6.f, 16.f, 14.f, "Time", " ", 1 / 2.f, 1000 * timeBase); 141 | configParam(LISSAJOUS_PARAM, 0.f, 1.f, 0.f, "X & Y / Lissajous"); 142 | configParam(TRIG_PARAM, -10.f, 10.f, 0.f, "Trigger position", " V"); 143 | configParam(EXTERNAL_PARAM, 0.f, 1.f, 0.f, "Internal / External Trigger"); 144 | 145 | configParam(KALEIDOSCOPE_USE_PARAM, 0.0f, 1.0f, 0.0f, "Kaleidoscope"); 146 | configParam(KALEIDOSCOPE_COUNT_PARAM, 3.0f, 12.0f, 3.0f, "Mirrors"); 147 | configParam(KALEIDOSCOPE_RADIUS_PARAM, 1.0f, 200.0f, 1.0f, "Radius"); 148 | 149 | configParam(LINE_TYPE_PARAM, 0.0f, NUM_LINES - 1, Scope::LineType::NORMAL_LINE, "Line Type"); 150 | configParam(LINE_WIDTH_PARAM, 0.1f, 10.0f, 1.5f, "Line Width"); 151 | configParam(LINE_HUE_PARAM, 0.0f, 1.0f, 1.0f, "Color"); 152 | configParam(LINE_FADE_PARAM, 0.0f, 1.0f, 1.0f, "Fade"); 153 | configParam(SHOW_STATS_PARAM, 0.0f, 1.0f, 0.0f, "Show Stats"); 154 | configParam(KALEIDOSCOPE_COLOR_SPREAD_PARAM, 0.0f, 1.0f, 0.0f, "Kaleidoscope Color Spread"); 155 | configParam(SHOW_LABELS_PARAM, 0.0f, 1.0f, 0.0f, "Show Labels"); 156 | configParam(PLOT_TYPE_PARAM, 0.0f, NUM_PLOT_TYPES - 1, Scope::PlotType::NORMAL, "Plot Type"); 157 | configParam(EXT_WINDOW_ALPHA_PARAM, 0.0f, 1.0f, 1.0f, "External Window Alpha"); 158 | } 159 | 160 | void onReset() override { 161 | params[LISSAJOUS_PARAM].setValue(false); 162 | params[EXTERNAL_PARAM].setValue(false); 163 | params[KALEIDOSCOPE_USE_PARAM].setValue(false); 164 | std::memset(bufferX, 0, sizeof(bufferX)); 165 | std::memset(bufferY, 0, sizeof(bufferY)); 166 | } 167 | 168 | void process(const ProcessArgs &args) override { 169 | //kaleidoscope parameters 170 | kaleidoscope.count = (int) clamp( 171 | params[KALEIDOSCOPE_COUNT_PARAM].getValue() + inputs[KALEIDOSCOPE_COUNT_INPUT].getVoltage(), 3.0f, 172 | 12.0f); 173 | kaleidoscope.radius = 174 | params[KALEIDOSCOPE_RADIUS_PARAM].getValue() + inputs[KALEIDOSCOPE_RADIUS_INPUT].getVoltage() * 10; 175 | 176 | hue = params[LINE_HUE_PARAM].getValue() + inputs[HUE_INPUT].getVoltage() / 10.0f; 177 | lineWidth = params[LINE_WIDTH_PARAM].getValue() + inputs[LINE_WIDTH_INPUT].getVoltage(); 178 | fade = params[LINE_FADE_PARAM].getValue(); 179 | 180 | //PLOT_TYPE_PARAM added after version 1.1.1 181 | //KALEIDOSCOPE_USE_PARAM and LISSAJOUS_PARAM are updated for compatibility 182 | auto pType = (int) (params[PLOT_TYPE_PARAM].getValue() + inputs[PLOT_TYPE_INPUT].getVoltage() / 3.0f); 183 | pType = clamp(pType, 0, NUM_PLOT_TYPES - 1); 184 | switch ((PlotType) pType) { 185 | case PlotType::NORMAL: 186 | params[KALEIDOSCOPE_USE_PARAM].setValue(false); 187 | params[LISSAJOUS_PARAM].setValue(false); 188 | break; 189 | case PlotType::KALEIDOSCOPE: 190 | params[KALEIDOSCOPE_USE_PARAM].setValue(true); 191 | params[LISSAJOUS_PARAM].setValue(true); 192 | break; 193 | case PlotType::LISSAJOUS: 194 | params[KALEIDOSCOPE_USE_PARAM].setValue(false); 195 | params[LISSAJOUS_PARAM].setValue(true); 196 | break; 197 | default: 198 | params[KALEIDOSCOPE_USE_PARAM].setValue(false); 199 | params[LISSAJOUS_PARAM].setValue(false); 200 | break; 201 | } 202 | 203 | // Compute time 204 | //updated to use cv 205 | auto deltaTime = std::pow(2.f, 206 | -clamp(params[TIME_PARAM].getValue() + abs(inputs[TIME_INPUT].getVoltage()), 6.0f, 207 | 16.0f)); 208 | auto frameCount = (int) std::ceil(deltaTime * args.sampleRate); 209 | 210 | // Set channels 211 | auto channelsX = inputs[X_INPUT].getChannels(); 212 | if (channelsX != this->channelsX) { 213 | std::memset(bufferX, 0, sizeof(bufferX)); 214 | this->channelsX = channelsX; 215 | } 216 | 217 | int channelsY = inputs[Y_INPUT].getChannels(); 218 | if (channelsY != this->channelsY) { 219 | std::memset(bufferY, 0, sizeof(bufferY)); 220 | this->channelsY = channelsY; 221 | } 222 | 223 | // Add frame to buffer 224 | if (bufferIndex < bufferSize) { 225 | if (++frameIndex > frameCount) { 226 | frameIndex = 0; 227 | for (auto c = 0; c < channelsX; c++) { 228 | bufferX[c][bufferIndex] = inputs[X_INPUT].getVoltage(c); 229 | } 230 | for (auto c = 0; c < channelsY; c++) { 231 | bufferY[c][bufferIndex] = inputs[Y_INPUT].getVoltage(c); 232 | } 233 | bufferIndex++; 234 | } 235 | } 236 | 237 | // Don't wait for trigger if still filling buffer 238 | if (bufferIndex < bufferSize) { 239 | return; 240 | } 241 | 242 | // Trigger immediately if external but nothing plugged in, or in Lissajous mode 243 | if ((bool) params[LISSAJOUS_PARAM].getValue() 244 | || ((bool) params[EXTERNAL_PARAM].getValue() && !inputs[TRIG_INPUT].isConnected())) { 245 | trigger(); 246 | return; 247 | } 248 | 249 | frameIndex++; 250 | 251 | // Reset if triggered 252 | auto trigThreshold = params[TRIG_PARAM].getValue(); 253 | trigThreshold += inputs[Scope::TRIG_LEVEL_INPUT].getVoltage(); 254 | trigThreshold = clamp(trigThreshold, -10.0f, 10.0f); 255 | Input &trigInput = (bool) params[EXTERNAL_PARAM].getValue() ? inputs[TRIG_INPUT] : inputs[X_INPUT]; 256 | 257 | // This may be 0 258 | auto trigChannels = trigInput.getChannels(); 259 | for (auto c = 0; c < trigChannels; c++) { 260 | auto trigVoltage = trigInput.getVoltage(c); 261 | if (triggers[c].process(rescale(trigVoltage, trigThreshold, trigThreshold + 0.001f, 0.f, 1.f))) { 262 | trigger(); 263 | return; 264 | } 265 | } 266 | 267 | // Reset if we've been waiting for `holdTime` 268 | const float holdTime = 0.1f; 269 | if (frameIndex * args.sampleTime >= holdTime) { 270 | trigger(); 271 | return; 272 | } 273 | } 274 | 275 | void trigger() { 276 | for (auto &trigger : triggers) { 277 | trigger.reset(); 278 | } 279 | bufferIndex = 0; 280 | frameIndex = 0; 281 | } 282 | 283 | json_t *dataToJson() override { 284 | json_t *rootJ = json_object(); 285 | json_object_set_new(rootJ, "WidgetWidth", json_real(widgetWidth.load())); 286 | json_object_set_new(rootJ, "bufferSize", json_integer(bufferSize)); 287 | return rootJ; 288 | } 289 | 290 | void dataFromJson(json_t *rootJ) override { 291 | json_t *ww = json_object_get(rootJ, "WidgetWidth"); 292 | if (ww) 293 | widgetWidth.store((float) json_real_value(ww)); 294 | 295 | json_t *bs = json_object_get(rootJ, "bufferSize"); 296 | if (bs) 297 | bufferSize = json_integer_value(bs); 298 | } 299 | }; 300 | 301 | // USER INTERFACE **************** 302 | 303 | /// interface to be implemented by module widget for popout window 304 | struct IPopupWindowOwner { 305 | virtual void IPopupWindowOwner_showWindow() = 0; 306 | 307 | virtual void IPopupWindowOwner_hideWindow() = 0; 308 | }; 309 | 310 | /// Placed on right of module, allow resizing of parent widget via drag 311 | struct ResizeTab : OpaqueWidget { 312 | Vec position = {}; 313 | Rect oldBounds = {}; 314 | 315 | ResizeTab() { 316 | box.size = Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT); 317 | } 318 | 319 | void onDragStart(const event::DragStart &e) override { 320 | if (e.button == GLFW_MOUSE_BUTTON_LEFT) { 321 | //save start size 322 | auto *modWidget = getAncestorOfType(); 323 | if (modWidget != nullptr) 324 | oldBounds = modWidget->box; 325 | 326 | // mousedown position 327 | position = APP->scene->rack->mousePos; 328 | } 329 | } 330 | 331 | void onDragMove(const event::DragMove &e) override { 332 | auto *modWidget = getAncestorOfType(); 333 | assert(modWidget); 334 | if (modWidget == nullptr) 335 | return; 336 | 337 | auto newPosition = APP->scene->rack->mousePos; 338 | auto xChange = newPosition.x - position.x; 339 | auto newRect = oldBounds; 340 | auto oldRect = modWidget->box; 341 | auto minW = 10 * RACK_GRID_WIDTH; 342 | 343 | newRect.size.x += xChange; 344 | newRect.size.x = std::max(newRect.size.x, minW); 345 | newRect.size.x = std::round(newRect.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH; 346 | 347 | //try to resize widget, or return to current size 348 | modWidget->box = newRect; 349 | if (!APP->scene->rack->requestModulePos(modWidget, newRect.pos)) 350 | modWidget->box = oldRect; 351 | } 352 | }; 353 | 354 | // No svg background is used 355 | // ScopePanel is used to draw background and border 356 | // allowing for resizeable widget 357 | struct ScopePanel : Widget { 358 | Widget *panelBorder = nullptr; 359 | NVGcolor backGroundColor; 360 | 361 | ScopePanel(NVGcolor color) { 362 | panelBorder = new PanelBorder; 363 | backGroundColor = color; 364 | addChild(panelBorder); 365 | } 366 | 367 | void step() override { 368 | // 369 | panelBorder->box.size = box.size; 370 | Widget::step(); 371 | } 372 | 373 | void draw(const DrawArgs &args) override { 374 | nvgBeginPath(args.vg); 375 | nvgRect(args.vg, 0, 0, box.size.x, box.size.y); 376 | nvgFillColor(args.vg, backGroundColor); 377 | nvgFill(args.vg); 378 | Widget::draw(args); 379 | } 380 | }; 381 | 382 | struct ScopeDisplay : ModuleLightWidget { 383 | Scope *module; 384 | int statsFrame = 0; 385 | std::shared_ptr font; 386 | Vec lastCoordinate; 387 | bool externalWindow = false; 388 | 389 | struct Stats { 390 | float vpp = 0.f; 391 | float vMin = 0.f; 392 | float vMax = 0.f; 393 | 394 | void calculate(float *buffer, int channels) { 395 | vMax = -INFINITY; 396 | vMin = INFINITY; 397 | for (auto i = 0; i < MAX_BUFFER_SIZE * channels; i++) { 398 | auto v = buffer[i]; 399 | vMax = std::fmax(vMax, v); 400 | vMin = std::fmin(vMin, v); 401 | } 402 | vpp = vMax - vMin; 403 | } 404 | }; 405 | 406 | Stats statsX, statsY; 407 | 408 | ScopeDisplay() { 409 | font = APP->window->loadFont(asset::system("res/fonts/ShareTechMono-Regular.ttf")); 410 | } 411 | 412 | void drawWaveform(const DrawArgs &args, 413 | const float *bufferX, 414 | float offsetX, 415 | float gainX, 416 | const float *bufferY, 417 | float offsetY, 418 | float gainY, 419 | float kRadius = 0.0f, 420 | float kRotation = 0.0f, 421 | NVGcolor beam = {1.0f, 1.0f, 1.0f, 1.0f}, 422 | Rect bounds = {0, 0, 1, 1}) { 423 | assert(bufferY); 424 | nvgSave(args.vg); 425 | 426 | //beam fading using a varying alpha 427 | auto maxAlpha = 0.99f; 428 | auto lightInc = maxAlpha / (float) module->bufferSize; 429 | auto currentAlpha = maxAlpha; 430 | 431 | auto currentLineWidth = module->lineWidth; 432 | auto widthInc = module->lineWidth / (float) module->bufferSize;; 433 | 434 | 435 | auto b = Rect(Vec(0, 15), bounds.size.minus(Vec(0, 15 * 2))); 436 | nvgBeginPath(args.vg); 437 | nvgScissor(args.vg, b.pos.x, b.pos.y, b.size.x, b.size.y); 438 | nvgTranslate(args.vg, kRadius * simd::cos(kRotation) + bounds.size.x / 2.0f, 439 | kRadius * simd::sin(kRotation) - (bounds.size.y - 30) / 2.0f); 440 | if (!(bool) module->params[Scope::LISSAJOUS_PARAM].getValue()) 441 | nvgTranslate(args.vg, -bounds.size.x / 2.0f, 0); 442 | 443 | nvgLineCap(args.vg, NVG_BUTT); 444 | nvgMiterLimit(args.vg, 2.f); 445 | nvgStrokeWidth(args.vg, module->lineWidth); 446 | nvgGlobalCompositeOperation(args.vg, NVG_LIGHTER); 447 | 448 | auto cos2R = simd::cos(2.0f * kRotation); 449 | auto sin2R = simd::sin(2.0f * kRotation); 450 | 451 | // when drawing the buffer, if the line is to fade, start drawing at 2 samples prior 452 | // bufferIndex, with full alpha. 453 | // when the line is not fading, draw the buffer from end to start to remove flicker. 454 | auto startIndex = (bool) module->fade ? module->bufferIndex - 3 : module->bufferSize - 2; 455 | startIndex = clamp(startIndex, 0, module->bufferSize - 1); 456 | auto endIndex = (bool) module->fade ? module->bufferIndex - 2 : 0; 457 | endIndex = clamp(endIndex, 1, module->bufferSize - 1); 458 | 459 | 460 | for (auto i = startIndex; i != endIndex; i--) { 461 | if (i < 0) 462 | i = module->bufferSize - 1; // loop buffer due to starting at various locations 463 | 464 | nvgStrokeColor(args.vg, nvgRGBAf(beam.r, beam.g, beam.b, currentAlpha)); 465 | nvgStrokeWidth(args.vg, currentLineWidth); 466 | if ((bool) module->fade) { 467 | currentAlpha -= lightInc; 468 | currentLineWidth -= widthInc; 469 | } 470 | Vec v; 471 | if (bufferX) { 472 | v.x = (bufferX[i] + offsetX) * gainX / 2.0f; 473 | } else { 474 | v.x = (float) i / (module->bufferSize - 1); 475 | } 476 | v.y = (bufferY[i] + offsetY) * gainY / 2.0f; 477 | 478 | //rotate 2 * kRotate 479 | 480 | auto xNew = v.x * cos2R + v.y * sin2R; 481 | v.y = -v.x * sin2R + v.y * cos2R; 482 | v.x = xNew; 483 | 484 | Vec p; 485 | // resize x & y by height, to keep plots in correct ratio if Lissajous 486 | if ((bool) module->params[Scope::LISSAJOUS_PARAM].getValue()) 487 | p.x = rescale(v.x, 0.f, 1.f, b.pos.x, b.pos.y + b.size.y); 488 | else 489 | p.x = rescale(v.x, 0.f, 1.f, b.pos.x, b.pos.x + b.size.x); 490 | 491 | p.y = rescale(v.y, 0.f, 1.f, b.pos.y + b.size.y, b.pos.y); 492 | if (i == module->bufferSize - 1) { 493 | nvgMoveTo(args.vg, p.x, p.y); 494 | lastCoordinate = p; 495 | } else { 496 | auto vectorScale = 0.998f; 497 | auto experimentalScale = 0.9f; 498 | auto lType = (int) (module->params[Scope::LINE_TYPE_PARAM].getValue() 499 | + module->inputs[Scope::LINE_TYPE_INPUT].getVoltage()); 500 | lType = clamp(lType, 0, (int) Scope::LineType::NUM_LINES - 1); 501 | switch ((Scope::LineType) lType) { 502 | case Scope::LineType::NORMAL_LINE: { 503 | //morph between normal and vector lines 504 | auto normVecCoeff = (module->params[Scope::LINE_TYPE_PARAM].getValue() 505 | + module->inputs[Scope::LINE_TYPE_INPUT].getVoltage()) 506 | - (int) Scope::LineType::NORMAL_LINE; 507 | Vec intermediate; 508 | intermediate.x = normVecCoeff * (p.x - lastCoordinate.x) + lastCoordinate.x; 509 | intermediate.y = normVecCoeff * (p.y - lastCoordinate.y) + lastCoordinate.y; 510 | if (i != startIndex) 511 | nvgMoveTo(args.vg, intermediate.x, intermediate.y); 512 | 513 | nvgLineTo(args.vg, p.x, p.y); 514 | break; 515 | } 516 | case Scope::LineType::VECTOR_LINE: { 517 | //morph between vector and experimental line types 518 | auto vecExprCoeff = (module->params[Scope::LINE_TYPE_PARAM].getValue() 519 | + module->inputs[Scope::LINE_TYPE_INPUT].getVoltage()) 520 | - (int) Scope::LineType::VECTOR_LINE; 521 | auto scale = vecExprCoeff * (experimentalScale - vectorScale) + vectorScale; 522 | 523 | nvgMoveTo(args.vg, scale * p.x, scale * p.y); 524 | nvgLineTo(args.vg, p.x, p.y); 525 | break; 526 | } 527 | case Scope::LineType::EXPERIMENTAL_LINE: 528 | nvgMoveTo(args.vg, experimentalScale * p.x, experimentalScale * p.y); 529 | nvgLineTo(args.vg, p.x, p.y); 530 | break; 531 | case Scope::NUM_LINES: 532 | assert(false); 533 | break; 534 | default: 535 | assert(false); 536 | } 537 | lastCoordinate = p; 538 | } 539 | nvgStroke(args.vg); 540 | nvgBeginPath(args.vg); 541 | nvgMoveTo(args.vg, p.x, p.y); 542 | lastCoordinate = p; 543 | } 544 | nvgResetTransform(args.vg); 545 | nvgResetScissor(args.vg); 546 | nvgRestore(args.vg); 547 | } 548 | 549 | void drawTrig(const DrawArgs &args, float value, Rect bounds) { 550 | auto b = Rect(Vec(0, 15), bounds.size.minus(Vec(0, 15 * 2))); 551 | nvgScissor(args.vg, b.pos.x, b.pos.y, b.size.x, b.size.y); 552 | 553 | value = value / 2.f + 0.5f; 554 | auto p = Vec(bounds.size.x, b.pos.y + b.size.y * (1.f - value)); 555 | 556 | // Draw line 557 | nvgStrokeColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0x10)); 558 | { 559 | nvgBeginPath(args.vg); 560 | nvgMoveTo(args.vg, p.x - 13, p.y); 561 | nvgLineTo(args.vg, 0, p.y); 562 | nvgClosePath(args.vg); 563 | } 564 | nvgStroke(args.vg); 565 | 566 | // Draw indicator 567 | nvgFillColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0x60)); 568 | { 569 | nvgBeginPath(args.vg); 570 | nvgMoveTo(args.vg, p.x - 2, p.y - 4); 571 | nvgLineTo(args.vg, p.x - 9, p.y - 4); 572 | nvgLineTo(args.vg, p.x - 13, p.y); 573 | nvgLineTo(args.vg, p.x - 9, p.y + 4); 574 | nvgLineTo(args.vg, p.x - 2, p.y + 4); 575 | nvgClosePath(args.vg); 576 | } 577 | nvgFill(args.vg); 578 | 579 | nvgFontSize(args.vg, 9); 580 | nvgFontFaceId(args.vg, font->handle); 581 | nvgFillColor(args.vg, nvgRGBA(0x1e, 0x28, 0x2b, 0xff)); 582 | nvgText(args.vg, p.x - 8, p.y + 3, "T", NULL); 583 | nvgResetScissor(args.vg); 584 | } 585 | 586 | void drawStats(const DrawArgs &args, Vec pos, const char *title, Stats *stats) { 587 | nvgFontSize(args.vg, 13); 588 | nvgFontFaceId(args.vg, font->handle); 589 | nvgTextLetterSpacing(args.vg, -2); 590 | 591 | nvgFillColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0x40)); 592 | nvgText(args.vg, pos.x + 6, pos.y + 11, title, NULL); 593 | 594 | nvgFillColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0x80)); 595 | pos = pos.plus(Vec(22, 11)); 596 | 597 | std::string text; 598 | text = "pp "; 599 | text += isNear(stats->vpp, 0.f, 100.f) ? string::f("% 6.2f", stats->vpp) : " ---"; 600 | nvgText(args.vg, pos.x, pos.y, text.c_str(), NULL); 601 | text = "max "; 602 | text += isNear(stats->vMax, 0.f, 100.f) ? string::f("% 6.2f", stats->vMax) : " ---"; 603 | nvgText(args.vg, pos.x + 58 * 1, pos.y, text.c_str(), NULL); 604 | text = "min "; 605 | text += isNear(stats->vMin, 0.f, 100.f) ? string::f("% 6.2f", stats->vMin) : " ---"; 606 | nvgText(args.vg, pos.x + 58 * 2, pos.y, text.c_str(), NULL); 607 | } 608 | 609 | void drawLabels(const DrawArgs &args) { 610 | std::vector labels = {"X Input", "X Scale", "X Position", "Y Input", "Y Scale", "Y Position", 611 | "Time", "Trigger Input", "Trigger Position", "Color", "Line Width", 612 | "Kaleidoscope Images", "Kaleidoscope Radius", "Color Spread", "Line Type", 613 | "Plot Type"}; 614 | nvgFontSize(args.vg, 13); 615 | nvgFontFaceId(args.vg, font->handle); 616 | nvgTextLetterSpacing(args.vg, -2); 617 | 618 | nvgFillColor(args.vg, nvgRGBA(0xff, 0xff, 0xff, 0x80)); 619 | auto y = 6.5f; 620 | for (const auto &l : labels) { 621 | nvgText(args.vg, mm2px(10.0f), mm2px(y), l.c_str(), NULL); 622 | y += 7.5; 623 | } 624 | } 625 | 626 | void draw(const DrawArgs &args) override { 627 | if (!module) 628 | return; 629 | 630 | // only display woweform in widget if the external window 631 | // is not open. The external window is drawn from ScopeWidget::step 632 | // where additional comments are found 633 | if (!externalWindow) 634 | preDrawWaveforms(args, box); 635 | 636 | // Calculate and draw stats 637 | if ((bool) module->params[Scope::SHOW_STATS_PARAM].getValue()) { 638 | if (++statsFrame >= 4) { 639 | statsFrame = 0; 640 | statsX.calculate(module->bufferX[0], module->channelsX); 641 | statsY.calculate(module->bufferY[0], module->channelsY); 642 | } 643 | drawStats(args, Vec(25, 0), "X", &statsX); 644 | drawStats(args, Vec(25, box.size.y - 15), "Y", &statsY); 645 | } 646 | 647 | if ((bool) module->params[Scope::SHOW_LABELS_PARAM].getValue()) { 648 | drawLabels(args); 649 | } 650 | 651 | LightWidget::draw(args); 652 | } 653 | 654 | void preDrawWaveforms(const DrawArgs &args, Rect bounds) { 655 | auto gainX = std::pow(2.f, module->params[Scope::X_SCALE_PARAM].getValue()) / 10.0f; 656 | gainX += module->inputs[Scope::X_SCALE_INPUT].getVoltage() / 10.0f; 657 | auto gainY = std::pow(2.f, module->params[Scope::Y_SCALE_PARAM].getValue()) / 10.0f; 658 | gainY += module->inputs[Scope::Y_SCALE_INPUT].getVoltage() / 10.0f; 659 | auto offsetX = module->params[Scope::X_POS_PARAM].getValue(); 660 | offsetX += module->inputs[Scope::X_POS_INPUT].getVoltage(); 661 | auto offsetY = module->params[Scope::Y_POS_PARAM].getValue(); 662 | offsetY += module->inputs[Scope::Y_POS_INPUT].getVoltage(); 663 | 664 | // Draw waveforms 665 | if ((bool) module->params[Scope::LISSAJOUS_PARAM].getValue()) { 666 | // X x Y 667 | auto lissajousChannels = std::max(module->channelsX, module->channelsY); 668 | for (auto c = 0; c < lissajousChannels; c++) { 669 | drawWaveform(args, 670 | module->bufferX[c], 671 | offsetX, 672 | gainX, 673 | module->bufferY[c], 674 | offsetY, 675 | gainY, 676 | 0, 677 | 0, 678 | nvgHSLA(module->hue, 0.5f, 0.5f, 200), 679 | bounds); 680 | 681 | //draw Kaleidoscope rotations; 682 | auto unitRotation = (float) (2.0 * M_PI) / (float) module->kaleidoscope.count; 683 | auto reflectionCount = !(bool) module->params[Scope::KALEIDOSCOPE_USE_PARAM].getValue() 684 | ? 0 685 | : module->kaleidoscope.count; 686 | auto unitHueChange = (module->params[Scope::KALEIDOSCOPE_COLOR_SPREAD_PARAM].getValue() 687 | + module->inputs[Scope::KALEIDOSCOPE_COLOR_SPREAD_INPUT].getVoltage() / 5.0) 688 | / reflectionCount; 689 | 690 | for (auto i = 0; i < reflectionCount; ++i) { 691 | auto hueChange = (i + 1) * unitHueChange; 692 | auto reflectionHue = std::fmod(module->hue + hueChange, 1.0f); 693 | drawWaveform(args, 694 | module->bufferX[c], 695 | offsetX, 696 | -gainX, 697 | module->bufferY[c], 698 | offsetY, 699 | gainY, 700 | module->kaleidoscope.radius, 701 | (i * unitRotation), 702 | nvgHSLA(reflectionHue, 0.5f, 0.5f, 200), 703 | bounds); 704 | } 705 | } 706 | } else { //draw normal 707 | // Y 708 | for (auto c = 0; c < module->channelsY; c++) { 709 | drawWaveform(args, 710 | NULL, 711 | 0, 712 | 0, 713 | module->bufferY[c], 714 | offsetY, 715 | gainY, 716 | 0, 717 | 0, 718 | nvgRGBA(0xe1, 0x02, 0x78, 0xc0), 719 | bounds); 720 | } 721 | 722 | // X 723 | for (auto c = 0; c < module->channelsX; c++) { 724 | drawWaveform(args, 725 | NULL, 726 | 0, 727 | 0, 728 | module->bufferX[c], 729 | offsetX, 730 | gainX, 731 | 0, 732 | 0, 733 | nvgHSLA(module->hue, 0.5f, 0.5f, 200), 734 | bounds); 735 | } 736 | 737 | auto trigThreshold = module->params[Scope::TRIG_PARAM].getValue(); 738 | trigThreshold += module->inputs[Scope::TRIG_LEVEL_INPUT].getVoltage(); 739 | trigThreshold = clamp(trigThreshold, -10.0f, 10.0f); 740 | trigThreshold = (trigThreshold + offsetX) * gainX; 741 | drawTrig(args, trigThreshold, bounds); 742 | } 743 | } 744 | }; 745 | 746 | //Context menus 747 | 748 | struct ShowWindowMenuItem : MenuItem { 749 | IPopupWindowOwner *windowOwner; 750 | 751 | void onAction(const event::Action &e) override { 752 | windowOwner->IPopupWindowOwner_showWindow(); 753 | } 754 | }; 755 | 756 | struct HideWindowMenuItem : MenuItem { 757 | IPopupWindowOwner *windowOwner; 758 | 759 | void onAction(const event::Action &e) override { 760 | windowOwner->IPopupWindowOwner_hideWindow(); 761 | } 762 | }; 763 | 764 | struct ExtWindowAlphaQuantity : Quantity { 765 | Scope *module = nullptr; 766 | 767 | void setValue(float value) override { 768 | if (module != nullptr) 769 | module->params[Scope::EXT_WINDOW_ALPHA_PARAM].setValue(clamp(value, 0.0f, 1.0f)); 770 | } 771 | 772 | float getValue() override { 773 | if (module == nullptr) 774 | return 0.0f; 775 | return module->params[Scope::EXT_WINDOW_ALPHA_PARAM].getValue(); 776 | } 777 | 778 | float getMinValue() override { 779 | return 0.0f; 780 | } 781 | 782 | float getMaxValue() override { 783 | return 1.0f; 784 | } 785 | 786 | float getDefaultValue() override { 787 | return 1.0f; 788 | } 789 | 790 | int getDisplayPrecision() override { 791 | return 2; 792 | } 793 | 794 | std::string getLabel() override { 795 | return "Pop-out Window Alpha"; 796 | } 797 | }; 798 | 799 | struct ExtWindowAlphaSlider : ui::Slider { 800 | ExtWindowAlphaSlider() { 801 | quantity = new ExtWindowAlphaQuantity; 802 | } 803 | 804 | ~ExtWindowAlphaSlider() { 805 | delete quantity; 806 | } 807 | }; 808 | 809 | struct PlotTypeMenuItem : MenuItem { 810 | Scope *module; 811 | Scope::PlotType plotType = Scope::PlotType::NORMAL; 812 | 813 | void onAction(const event::Action &e) override { 814 | module->params[Scope::PLOT_TYPE_PARAM].setValue(plotType); 815 | } 816 | }; 817 | 818 | struct LineTypeMenuItem : MenuItem { 819 | Scope *module; 820 | Scope::LineType lineType = Scope::LineType::NORMAL_LINE; 821 | 822 | void onAction(const event::Action &e) override { 823 | module->params[Scope::LINE_TYPE_PARAM].setValue(lineType); 824 | } 825 | }; 826 | 827 | struct ResolutionMenuItem : MenuItem { 828 | Scope *module; 829 | int size = 512; 830 | 831 | void onAction(const event::Action &e) override { 832 | module->bufferSize = size; 833 | } 834 | }; 835 | 836 | struct ExternalTriggerMenuItem : MenuItem { 837 | Scope *module; 838 | 839 | void onAction(const event::Action &e) override { 840 | module->params[Scope::EXTERNAL_PARAM].setValue 841 | (!(bool) module->params[Scope::EXTERNAL_PARAM].getValue()); 842 | } 843 | }; 844 | 845 | struct LineFadeMenuItem : MenuItem { 846 | Scope *module; 847 | 848 | void onAction(const event::Action &e) override { 849 | module->params[Scope::LINE_FADE_PARAM].setValue 850 | (!(bool) module->params[Scope::LINE_FADE_PARAM].getValue()); 851 | } 852 | }; 853 | 854 | struct ShowStatsMenuItem : MenuItem { 855 | Scope *module; 856 | 857 | void onAction(const event::Action &e) override { 858 | module->params[Scope::SHOW_STATS_PARAM].setValue 859 | (!(bool) module->params[Scope::SHOW_STATS_PARAM].getValue()); 860 | } 861 | }; 862 | 863 | struct ShowLabelsMenuItem : MenuItem { 864 | Scope *module; 865 | 866 | void onAction(const event::Action &e) override { 867 | module->params[Scope::SHOW_LABELS_PARAM].setValue 868 | (!(bool) module->params[Scope::SHOW_LABELS_PARAM].getValue()); 869 | } 870 | }; 871 | 872 | struct TinyKnob : RoundKnob { 873 | TinyKnob() { 874 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/scopeTinyKnob.svg"))); 875 | } 876 | }; 877 | 878 | struct TinySnapKnob : RoundKnob { 879 | TinySnapKnob() { 880 | snap = true; 881 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/scopeTinyKnob.svg"))); 882 | } 883 | }; 884 | 885 | struct TinyPort : app::SvgPort { 886 | TinyPort() { 887 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/scopeTinyPort.svg"))); 888 | } 889 | }; 890 | 891 | struct Port2mm : app::SvgPort { 892 | Port2mm() { 893 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance, "res/scope2mmPort.svg"))); 894 | } 895 | }; 896 | 897 | // Components 898 | 899 | struct ScopeWidget : ModuleWidget, IPopupWindowOwner { 900 | ResizeTab rt; 901 | ScopeDisplay *display; 902 | GLFWwindow *_window = nullptr; // Handle to the popup window. 903 | NVGcontext *_vg = nullptr; // For painting into the popup window. 904 | std::shared_ptr _font; // 905 | 906 | ScopeWidget(Scope *module) { 907 | setModule(module); 908 | box.size = Vec(RACK_GRID_WIDTH * 17, RACK_GRID_HEIGHT); 909 | { 910 | panel = new ScopePanel(nvgRGB(5, 5, 30)); 911 | panel->box.size = box.size; 912 | addChild(panel); 913 | } 914 | { 915 | auto leftBorder = 0; 916 | display = new ScopeDisplay(); 917 | display->module = module; 918 | display->box.pos = Vec(leftBorder, 0); 919 | display->box.size = Vec(box.size.x - leftBorder, box.size.y); 920 | addChild(display); 921 | } 922 | //add resize tab to ui 923 | rt.box.pos.x = box.size.x - RACK_GRID_WIDTH; 924 | rt.box.pos.y = 0.0f; 925 | addChild(&rt); 926 | 927 | addInput(createInputCentered(mm2px(Vec(5, 5)), module, Scope::X_INPUT)); 928 | addParam(createParamCentered(mm2px(Vec(5, 12.5)), module, Scope::X_SCALE_PARAM)); 929 | addInput(createInputCentered(mm2px(Vec(5, 12.5)), module, Scope::X_SCALE_INPUT)); 930 | addParam(createParamCentered(mm2px(Vec(5, 20)), module, Scope::X_POS_PARAM)); 931 | addInput(createInputCentered(mm2px(Vec(5, 20)), module, Scope::X_POS_INPUT)); 932 | addInput(createInputCentered(mm2px(Vec(5, 27.5)), module, Scope::Y_INPUT)); 933 | addParam(createParamCentered(mm2px(Vec(5, 35)), module, Scope::Y_SCALE_PARAM)); 934 | addInput(createInputCentered(mm2px(Vec(5, 35)), module, Scope::Y_SCALE_INPUT)); 935 | addParam(createParamCentered(mm2px(Vec(5, 42.5)), module, Scope::Y_POS_PARAM)); 936 | addInput(createInputCentered(mm2px(Vec(5, 42.5)), module, Scope::Y_POS_INPUT)); 937 | addParam(createParamCentered(mm2px(Vec(5, 50)), module, Scope::TIME_PARAM)); 938 | addInput(createInputCentered(mm2px(Vec(5, 50)), module, Scope::TIME_INPUT)); 939 | addInput(createInputCentered(mm2px(Vec(5, 57.5)), module, Scope::TRIG_INPUT)); 940 | addParam(createParamCentered(mm2px(Vec(5, 65)), module, Scope::TRIG_PARAM)); 941 | addInput(createInputCentered(mm2px(Vec(5, 65)), module, Scope::TRIG_LEVEL_INPUT)); 942 | addParam(createParamCentered(mm2px(Vec(5, 72.5)), module, Scope::LINE_HUE_PARAM)); 943 | addInput(createInputCentered(mm2px(Vec(5, 72.5)), module, Scope::HUE_INPUT)); 944 | addParam((createParamCentered(mm2px(Vec(5, 80.0)), module, Scope::LINE_WIDTH_PARAM))); 945 | addInput(createInputCentered(mm2px(Vec(5, 80.0)), module, Scope::LINE_WIDTH_INPUT)); 946 | addParam(createParamCentered(mm2px(Vec(5, 87.5)), module, Scope::KALEIDOSCOPE_COUNT_PARAM)); 947 | addInput(createInputCentered(mm2px(Vec(5, 87.5)), module, Scope::KALEIDOSCOPE_COUNT_INPUT)); 948 | addParam(createParamCentered(mm2px(Vec(5, 95.0)), module, Scope::KALEIDOSCOPE_RADIUS_PARAM)); 949 | addInput(createInputCentered(mm2px(Vec(5, 95.0)), module, Scope::KALEIDOSCOPE_RADIUS_INPUT)); 950 | addParam(createParamCentered(mm2px(Vec(5, 102.5)), module, Scope::KALEIDOSCOPE_COLOR_SPREAD_PARAM)); 951 | addInput(createInputCentered(mm2px(Vec(5, 102.5)), module, Scope::KALEIDOSCOPE_COLOR_SPREAD_INPUT)); 952 | addParam(createParamCentered(mm2px(Vec(5, 110.0)), module, Scope::LINE_TYPE_PARAM)); 953 | addInput(createInputCentered(mm2px(Vec(5, 110.0)), module, Scope::LINE_TYPE_INPUT)); 954 | addParam(createParamCentered(mm2px(Vec(5, 117.5)), module, Scope::PLOT_TYPE_PARAM)); 955 | addInput(createInputCentered(mm2px(Vec(5, 117.5)), module, Scope::PLOT_TYPE_INPUT)); 956 | } 957 | 958 | ~ScopeWidget() override { 959 | removeChild(&rt); 960 | // Hide the pop out window if we have one 961 | IPopupWindowOwner_hideWindow(); 962 | } 963 | 964 | 965 | void step() override { 966 | //pop-out window is handled here, I would have preferred ScopeDisplay::draw() 967 | //but this only updates the external window if the ModuleWidget is displayed 968 | //zooming and scrolling in the main window can stop rendering of the external 969 | //window 970 | if (_window) { 971 | display->externalWindow = true; 972 | // Paint the popup window. 973 | glfwMakeContextCurrent(_window); 974 | 975 | // Get the size of the popup window, both the window and the framebuffer. 976 | int winWidth, winHeight; 977 | int fbWidth, fbHeight; 978 | float pxRatio; 979 | glfwGetWindowSize(_window, &winWidth, &winHeight); 980 | glfwGetFramebufferSize(_window, &fbWidth, &fbHeight); 981 | pxRatio = (float) fbWidth / (float) winWidth; 982 | 983 | // Start painting. 984 | glViewport(0, 0, fbWidth, fbHeight); 985 | auto alpha = dynamic_cast(module)->params[Scope::EXT_WINDOW_ALPHA_PARAM].getValue(); 986 | glClearColor(0, 0, 0, alpha); //Alpha background 987 | glClear(GL_COLOR_BUFFER_BIT); 988 | nvgBeginFrame(_vg, (float) winWidth, (float) winHeight, pxRatio); 989 | 990 | DrawArgs context; 991 | context.vg = _vg; 992 | display->preDrawWaveforms(context, Rect(0, 0, fbWidth, fbHeight)); 993 | 994 | // Finished painting. 995 | nvgEndFrame(_vg); 996 | glfwSwapBuffers(_window); 997 | glfwMakeContextCurrent(APP->window->win); 998 | 999 | // If the user has clicked the window's Close button, close it. 1000 | if (glfwWindowShouldClose(_window)) { 1001 | IPopupWindowOwner_hideWindow(); 1002 | } 1003 | } else 1004 | display->externalWindow = false; 1005 | 1006 | if (box.size.x != panel->box.size.x) { // ui resized 1007 | if (module) 1008 | ((Scope *) (module))->widgetWidth.store(box.size.x); 1009 | } 1010 | 1011 | if (module) 1012 | box.size.x = ((Scope *) (module))->widgetWidth.load(); 1013 | 1014 | panel->setSize(box.size); 1015 | 1016 | display->box.size = Vec(box.size.x, box.size.y); 1017 | rt.box.pos.x = box.size.x - rt.box.size.x; 1018 | rt.box.pos.y = 0; 1019 | ModuleWidget::step(); 1020 | } 1021 | 1022 | void IPopupWindowOwner_showWindow() override { 1023 | if (_window == nullptr) { 1024 | // Tell GLFW the properties of the window we want to create. 1025 | glfwWindowHint(GLFW_MAXIMIZED, GLFW_FALSE); 1026 | glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); 1027 | glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); 1028 | glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); 1029 | 1030 | // Create the window. 1031 | _window = glfwCreateWindow(400, 300, "Opsylloscope", NULL, NULL); 1032 | 1033 | 1034 | // Don't wait for vsync when rendering 'cos it slows down the Rack UI thread. 1035 | glfwMakeContextCurrent(_window); 1036 | glfwSwapInterval(0); 1037 | 1038 | // If you want your window to stay on top of other windows. 1039 | // glfwSetWindowAttrib(_window, GLFW_FLOATING, true); 1040 | 1041 | // Create a NanoVG context for painting the popup window. 1042 | // _vg = nvgCreateGL2(NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG); 1043 | _vg = nvgCreateGL2(0); 1044 | 1045 | // Hand OpenGL back to Rack. 1046 | glfwMakeContextCurrent(APP->window->win); 1047 | } 1048 | } 1049 | 1050 | void IPopupWindowOwner_hideWindow() override { 1051 | if (_window != nullptr) { 1052 | // Destroy the window and its NanoVG context. 1053 | glfwMakeContextCurrent(_window); 1054 | nvgDeleteGL2(_vg); 1055 | glfwDestroyWindow(_window); 1056 | glfwMakeContextCurrent(APP->window->win); 1057 | _window = nullptr; 1058 | } 1059 | } 1060 | 1061 | void appendContextMenu(Menu *menu) override { 1062 | auto *module = dynamic_cast (this->module); 1063 | 1064 | menu->addChild(new MenuEntry); 1065 | 1066 | if (_window == nullptr) { 1067 | ShowWindowMenuItem *showWindowMenuItem = new ShowWindowMenuItem; 1068 | showWindowMenuItem->text = "Display in pop-out window"; 1069 | showWindowMenuItem->windowOwner = this; 1070 | menu->addChild(showWindowMenuItem); 1071 | } else { 1072 | HideWindowMenuItem *hideWindowMenuItem = new HideWindowMenuItem; 1073 | hideWindowMenuItem->text = "Hide pop-out window"; 1074 | hideWindowMenuItem->windowOwner = this; 1075 | menu->addChild(hideWindowMenuItem); 1076 | } 1077 | 1078 | 1079 | auto *extWindowAlphaSlider = new ExtWindowAlphaSlider; 1080 | dynamic_cast(extWindowAlphaSlider->quantity)->module = module; 1081 | extWindowAlphaSlider->box.size.x = 200.0f; 1082 | menu->addChild(extWindowAlphaSlider); 1083 | 1084 | menu->addChild(new MenuEntry); 1085 | 1086 | 1087 | auto *plotTypeLabel = new MenuLabel(); 1088 | plotTypeLabel->text = "Plot Type"; 1089 | menu->addChild(plotTypeLabel); 1090 | 1091 | auto *normalPlotType = new PlotTypeMenuItem(); 1092 | normalPlotType->plotType = Scope::PlotType::NORMAL; 1093 | normalPlotType->text = "Normal"; 1094 | normalPlotType->rightText = CHECKMARK( 1095 | module->params[Scope::PLOT_TYPE_PARAM].getValue() == Scope::PlotType::NORMAL); 1096 | normalPlotType->module = module; 1097 | menu->addChild(normalPlotType); 1098 | 1099 | auto *lissajousPlotType = new PlotTypeMenuItem(); 1100 | lissajousPlotType->plotType = Scope::PlotType::LISSAJOUS; 1101 | lissajousPlotType->text = "Lissajous"; 1102 | lissajousPlotType->rightText = CHECKMARK( 1103 | module->params[Scope::PLOT_TYPE_PARAM].getValue() == Scope::PlotType::LISSAJOUS); 1104 | lissajousPlotType->module = module; 1105 | menu->addChild(lissajousPlotType); 1106 | 1107 | auto *kaleidoscope = new PlotTypeMenuItem(); 1108 | kaleidoscope->plotType = Scope::PlotType::KALEIDOSCOPE; 1109 | kaleidoscope->text = "Kaleidoscope"; 1110 | kaleidoscope->rightText = CHECKMARK( 1111 | module->params[Scope::PLOT_TYPE_PARAM].getValue() == Scope::PlotType::KALEIDOSCOPE); 1112 | kaleidoscope->module = module; 1113 | menu->addChild(kaleidoscope); 1114 | 1115 | menu->addChild(new MenuEntry); 1116 | 1117 | auto *lineTypeLabel = new MenuLabel(); 1118 | lineTypeLabel->text = "LineType"; 1119 | menu->addChild(lineTypeLabel); 1120 | 1121 | auto *normalLineType = new LineTypeMenuItem(); 1122 | normalLineType->text = "Normal"; 1123 | normalLineType->lineType = Scope::LineType::NORMAL_LINE; 1124 | normalLineType->rightText = CHECKMARK(module->params[Scope::LINE_TYPE_PARAM].getValue() 1125 | == Scope::LineType::NORMAL_LINE); 1126 | normalLineType->module = module; 1127 | menu->addChild(normalLineType); 1128 | 1129 | auto *vectorLineType = new LineTypeMenuItem(); 1130 | vectorLineType->text = "Vector"; 1131 | vectorLineType->lineType = Scope::LineType::VECTOR_LINE; 1132 | vectorLineType->rightText = CHECKMARK(module->params[Scope::LINE_TYPE_PARAM].getValue() 1133 | == Scope::LineType::VECTOR_LINE); 1134 | vectorLineType->module = module; 1135 | menu->addChild(vectorLineType); 1136 | 1137 | auto *experimentalLineType = new LineTypeMenuItem(); 1138 | experimentalLineType->text = "Experimental"; 1139 | experimentalLineType->lineType = Scope::LineType::EXPERIMENTAL_LINE; 1140 | experimentalLineType->rightText = CHECKMARK(module->params[Scope::LINE_TYPE_PARAM].getValue() 1141 | == Scope::LineType::EXPERIMENTAL_LINE); 1142 | experimentalLineType->module = module; 1143 | menu->addChild(experimentalLineType); 1144 | 1145 | menu->addChild(new MenuEntry); 1146 | 1147 | auto *triggerTypeLabel = new MenuLabel(); 1148 | triggerTypeLabel->text = "Trigger"; 1149 | menu->addChild(triggerTypeLabel); 1150 | 1151 | auto *extTrig = new ExternalTriggerMenuItem(); 1152 | extTrig->text = "External"; 1153 | extTrig->rightText = CHECKMARK(module->params[Scope::EXTERNAL_PARAM].getValue()); 1154 | extTrig->module = module; 1155 | menu->addChild(extTrig); 1156 | 1157 | menu->addChild(new MenuEntry); 1158 | 1159 | auto *fade = new LineFadeMenuItem(); 1160 | fade->text = "Fade"; 1161 | fade->rightText = CHECKMARK(module->params[Scope::LINE_FADE_PARAM].getValue()); 1162 | fade->module = module; 1163 | menu->addChild(fade); 1164 | 1165 | auto *showStats = new ShowStatsMenuItem(); 1166 | showStats->text = "Show Stats"; 1167 | showStats->rightText = CHECKMARK(module->params[Scope::SHOW_STATS_PARAM].getValue()); 1168 | showStats->module = module; 1169 | menu->addChild(showStats); 1170 | 1171 | auto *showLabels = new ShowLabelsMenuItem(); 1172 | showLabels->text = "Show Labels"; 1173 | showLabels->rightText = CHECKMARK(module->params[Scope::SHOW_LABELS_PARAM].getValue()); 1174 | showLabels->module = module; 1175 | menu->addChild(showLabels); 1176 | 1177 | menu->addChild(new MenuEntry); 1178 | 1179 | auto *bufferSizeLabel = new MenuLabel(); 1180 | bufferSizeLabel->text = "Performance"; 1181 | menu->addChild(bufferSizeLabel); 1182 | 1183 | auto *resolution512 = new ResolutionMenuItem(); 1184 | resolution512->module = module; 1185 | resolution512->size = 512; 1186 | resolution512->text = "Eco"; 1187 | resolution512->rightText = CHECKMARK(module->bufferSize == 512); 1188 | menu->addChild(resolution512); 1189 | 1190 | auto *resolution1024 = new ResolutionMenuItem(); 1191 | resolution1024->module = module; 1192 | resolution1024->size = 1024; 1193 | resolution1024->text = "Normal"; 1194 | resolution1024->rightText = CHECKMARK(module->bufferSize == 1024); 1195 | menu->addChild(resolution1024); 1196 | 1197 | auto *resolution2048 = new ResolutionMenuItem(); 1198 | resolution2048->module = module; 1199 | resolution2048->size = 2048; 1200 | resolution2048->text = "Good"; 1201 | resolution2048->rightText = CHECKMARK(module->bufferSize == 2048); 1202 | menu->addChild(resolution2048); 1203 | 1204 | auto *resolution4096 = new ResolutionMenuItem(); 1205 | resolution4096->module = module; 1206 | resolution4096->size = 4096; 1207 | resolution4096->text = "Ultra"; 1208 | resolution4096->rightText = CHECKMARK(module->bufferSize == 4096); 1209 | menu->addChild(resolution4096); 1210 | } 1211 | }; 1212 | 1213 | Model *modelOpsylloscope = createModel("Opsylloscope"); 1214 | --------------------------------------------------------------------------------