├── .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 |   
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 | 
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 |
65 |
--------------------------------------------------------------------------------
/res/scopeTinyKnob.svg:
--------------------------------------------------------------------------------
1 |
2 |
90 |
--------------------------------------------------------------------------------
/res/scopeTinyPort.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
--------------------------------------------------------------------------------