├── .github └── workflows │ ├── docs.yml │ └── main.yml ├── .gitignore ├── .gitmodules ├── .vscode ├── STM32H750x.svd ├── c_cpp_properties.json ├── launch.json └── tasks.json ├── Examples ├── Blink │ ├── Blink.cpp │ └── Makefile ├── Ringmod │ ├── Makefile │ └── Ringmod.cpp └── Volume │ ├── Makefile │ └── Volume.cpp ├── README.md ├── ci ├── build_dist.py ├── build_examples.py ├── build_libs.sh ├── copy_readme.sh ├── local_style_check.sh └── rm_readme.sh ├── dist ├── Examples │ ├── Blink.bin │ ├── Ringmod.bin │ └── Volume.bin └── examples.json ├── docs ├── Doxyfile ├── DoxygenLayout.xml ├── extra │ ├── doxygen-awesome-darkmode-toggle.js │ ├── doxygen-awesome-sidebar-only-darkmode-toggle.css │ ├── doxygen-awesome-sidebar-only.css │ ├── doxygen-awesome.css │ ├── footer.html │ └── header.html ├── images │ └── QB_Logo_Small-55x55.png └── md │ └── a1_Building the Examples.md ├── include └── aurora.h └── rebuild_all.sh /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | # Generates and Deploys doxygen docs 3 | on: 4 | push: 5 | branches: [ main ] # only gen docs for pushes to main 6 | paths: 7 | - 'include/*' 8 | - 'docs/*' 9 | - './README.md' 10 | workflow_dispatch: 11 | 12 | jobs: 13 | # creates a dist/documentation/* folder containing the html documentation 14 | deploy-local: 15 | permissions: 16 | contents: write 17 | checks: write 18 | pull-requests: write 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: mattnotmitt/doxygen-action@v1 23 | with: 24 | doxyfile-path: docs/Doxyfile 25 | - uses: EndBug/add-and-commit@v9 26 | with: 27 | message: 'Automated: commiting doxygen reference' 28 | add: dist/documentation 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | - name: deploy web 32 | uses: peaceiris/actions-gh-pages@v3 33 | with: 34 | github_token: ${{ secrets.GITHUB_TOKEN }} 35 | publish_dir: ./dist/documentation/html 36 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build All 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | submodules: 'recursive' 16 | - name: arm-none-eabi-gcc 17 | uses: fiam/arm-none-eabi-gcc@v1 18 | with: 19 | release: '9-2019-q4' 20 | - name: Build Libraries 21 | run: ./ci/build_libs.sh 22 | - name: setup python 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: 3.8 26 | - name: Build Examples 27 | run: python ./ci/build_examples.py 28 | - name: Create Distributable Binaries 29 | run: python ./ci/build_dist.py -ru 30 | - name: Deploy Dist 31 | uses: EndBug/add-and-commit@v4 32 | with: 33 | message: 'Automated: Committing Dist folder' 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libDaisy"] 2 | path = libs/libDaisy 3 | url = https://github.com/electro-smith/libDaisy 4 | [submodule "DaisySP"] 5 | path = libs/DaisySP 6 | url = https://github.com/electro-smith/DaisySP 7 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Win32", 5 | // Edit to match your development environment if necessary 6 | "compilerPath": "C:/Program Files/DaisyToolchain/bin/arm-none-eabi-gcc.exe", 7 | "includePath": [ 8 | "${workspaceFolder}/Examples/**", 9 | "${workspaceFolder}/include/**", 10 | "${workspaceFolder}/libs/libDaisy//**", 11 | "${workspaceFolder}/libs/DaisySP//**" 12 | ], 13 | "intelliSenseMode": "gcc-x64", 14 | "cStandard": "c11", 15 | "cppStandard": "c++17", 16 | "defines": [ 17 | "DEBUG=1", 18 | "STM32H750xx" 19 | ], 20 | "windowsSdkVersion": "10.0.17763.0" 21 | }, 22 | { 23 | "name": "Mac", 24 | // Edit to match your development environment if necessary 25 | "compilerPath": "/usr/bin/arm-none-eabi-gcc", 26 | "includePath": [ 27 | "${workspaceFolder}/Examples/**", 28 | "${workspaceFolder}/include/**", 29 | "${workspaceFolder}/libs/libDaisy//**", 30 | "${workspaceFolder}/libs/DaisySP//**" 31 | ], 32 | "intelliSenseMode": "gcc-x64", 33 | "cStandard": "c11", 34 | "cppStandard": "c++17", 35 | "defines": [ 36 | "DEBUG=1", 37 | "STM32H750xx" 38 | ] 39 | } 40 | ], 41 | "version": 4 42 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "configFiles": [ 5 | "interface/stlink.cfg", 6 | "target/stm32h7x.cfg" 7 | ], 8 | "cwd": "${workspaceFolder}", 9 | "debuggerArgs": [ 10 | "-d", 11 | "${workspaceRoot}" 12 | ], 13 | // Here's where you can put the path to the program you want to debug: 14 | "executable": "${workspaceRoot}/Examples/Blink/build/Blink.elf", 15 | "interface": "swd", 16 | "name": "Cortex Debug", 17 | "openOCDLaunchCommands": [ 18 | "init", 19 | "reset init", 20 | "gdb_breakpoint_override hard" 21 | ], 22 | //"preLaunchTask": "build_all_debug", 23 | "preRestartCommands": [ 24 | "load", 25 | "enable breakpoint", 26 | "monitor reset" 27 | ], 28 | "request": "launch", 29 | "runToMain": true, 30 | "servertype": "openocd", 31 | "showDevDebugOutput": true, 32 | "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", 33 | "type": "cortex-debug" 34 | } 35 | ], 36 | "version": "0.2.0" 37 | } 38 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "label": "Build Libraries", 5 | "command": "./ci/build_libs.sh", 6 | "options": { 7 | "cwd": "${workspaceFolder}" 8 | }, 9 | "problemMatcher": [], 10 | "type": "shell" 11 | }, 12 | { 13 | "label": "Build Example - Blink", 14 | "command": "make", 15 | "options": { 16 | "cwd": "${workspaceFolder}/Examples/Blink" 17 | }, 18 | "problemMatcher": [], 19 | "type": "shell" 20 | }, 21 | { 22 | "label": "Build Example - Volume", 23 | "command": "make", 24 | "options": { 25 | "cwd": "${workspaceFolder}/Examples/Volume" 26 | }, 27 | "problemMatcher": [], 28 | "type": "shell" 29 | }, 30 | { 31 | "label": "Build Example - Ringmod", 32 | "command": "make", 33 | "options": { 34 | "cwd": "${workspaceFolder}/Examples/Ringmod" 35 | }, 36 | "problemMatcher": [], 37 | "type": "shell" 38 | }, 39 | { 40 | "label": "Build", 41 | "dependsOn": [ 42 | "Build Example - Blink", 43 | "Build Example - Volume", 44 | "Build Example - Ringmod" 45 | ], 46 | "problemMatcher": [], 47 | "group": { 48 | "kind": "build", 49 | "isDefault": true 50 | } 51 | }, 52 | { 53 | "label": "Clean Example - Blink", 54 | "command": "make clean", 55 | "options": { 56 | "cwd": "${workspaceFolder}/Examples/Blink" 57 | }, 58 | "problemMatcher": [], 59 | "type": "shell" 60 | }, 61 | { 62 | "label": "Clean Example - Volume", 63 | "command": "make clean", 64 | "options": { 65 | "cwd": "${workspaceFolder}/Examples/Volume" 66 | }, 67 | "problemMatcher": [], 68 | "type": "shell" 69 | }, 70 | { 71 | "label": "Clean Example - Ringmod", 72 | "command": "make clean", 73 | "options": { 74 | "cwd": "${workspaceFolder}/Examples/Ringmod" 75 | }, 76 | "problemMatcher": [], 77 | "type": "shell" 78 | }, 79 | { 80 | "label": "Clean", 81 | "dependsOn": [ 82 | "Clean Example - Blink", 83 | "Clean Example - Volume", 84 | "Clean Example - Ringmod" 85 | ], 86 | "problemMatcher": [], 87 | } 88 | ], 89 | "version": "2.0.0" 90 | } -------------------------------------------------------------------------------- /Examples/Blink/Blink.cpp: -------------------------------------------------------------------------------- 1 | /** Blink 2 | * 3 | * Demonstrates blinking of a single LED 4 | * 5 | * In this example, the Freeze LED will blink on/off 6 | * once every second. 7 | */ 8 | #include "aurora.h" 9 | 10 | using namespace daisy; 11 | using namespace aurora; 12 | 13 | /** Our global hardware object */ 14 | Hardware hw; 15 | 16 | int main(void) 17 | { 18 | /** Initialize the Hardware */ 19 | hw.Init(); 20 | 21 | /** Create a variable to hold the state of the LED */ 22 | bool led_state = true; 23 | 24 | /** Infinite Loop */ 25 | while (1) 26 | { 27 | /** Set the State of the LED */ 28 | hw.SetLed(LED_FREEZE, 0.f, 0.f, led_state); 29 | 30 | /** Toggle the state */ 31 | if (led_state) 32 | led_state = false; 33 | else 34 | led_state = true; 35 | 36 | /** Write all LED states to the hardware */ 37 | hw.WriteLeds(); 38 | 39 | /** Delay 500ms */ 40 | System::Delay(500); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Examples/Blink/Makefile: -------------------------------------------------------------------------------- 1 | # Project Name 2 | TARGET = Blink 3 | 4 | # Build Project for Daisy Bootloader 5 | APP_TYPE = BOOT_SRAM 6 | 7 | # Sources 8 | CPP_SOURCES = Blink.cpp 9 | 10 | # Path to the root of the Aurora-SDK 11 | # When building custom applications outside of this repo 12 | # update this to point to the Aurora-SDK/ folder 13 | AURORA_SDK_PATH = ../.. 14 | 15 | # Location of Hardware Support File within the SDK 16 | C_INCLUDES += -I$(AURORA_SDK_PATH)/include/ 17 | 18 | # Library Locations 19 | LIBDAISY_DIR = $(AURORA_SDK_PATH)/libs/libDaisy/ 20 | DAISYSP_DIR = $(AURORA_SDK_PATH)/libs/DaisySP/ 21 | 22 | # To DEBUG the project with an ST-Link Probe: 23 | # 1. Compile the program with the below lines uncommented 24 | # 2. Load the firmware via the USB drive 25 | # 3. Make sure your .vscode/launch.json points to the 26 | # build/*.elf for the desired program 27 | # 4. Navigate and run the "Cortex Debug" Run and Debug configuration 28 | # or simply press F5 in VS Code. 29 | 30 | # DEBUG = 1 31 | # OPT = -O0 32 | 33 | # Core location, and generic Makefile. 34 | SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core 35 | include $(SYSTEM_FILES_DIR)/Makefile 36 | -------------------------------------------------------------------------------- /Examples/Ringmod/Makefile: -------------------------------------------------------------------------------- 1 | # Project Name 2 | TARGET = Ringmod 3 | 4 | # Build Project for Daisy Bootloader 5 | APP_TYPE = BOOT_SRAM 6 | 7 | # Sources 8 | CPP_SOURCES = Ringmod.cpp 9 | 10 | # Path to the root of the Aurora-SDK 11 | # When building custom applications outside of this repo 12 | # update this to point to the Aurora-SDK/ folder 13 | AURORA_SDK_PATH = ../.. 14 | 15 | # Location of Hardware Support File within the SDK 16 | C_INCLUDES += -I$(AURORA_SDK_PATH)/include/ 17 | 18 | # Library Locations 19 | LIBDAISY_DIR = $(AURORA_SDK_PATH)/libs/libDaisy/ 20 | DAISYSP_DIR = $(AURORA_SDK_PATH)/libs/DaisySP/ 21 | 22 | # To DEBUG the project with an ST-Link Probe: 23 | # 1. Compile the program with the below lines uncommented 24 | # 2. Load the firmware via the USB drive 25 | # 3. Make sure your .vscode/launch.json points to the 26 | # build/*.elf for the desired program 27 | # 4. Navigate and run the "Cortex Debug" Run and Debug configuration 28 | # or simply press F5 in VS Code. 29 | 30 | # DEBUG = 1 31 | # OPT = -O0 32 | 33 | # Core location, and generic Makefile. 34 | SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core 35 | include $(SYSTEM_FILES_DIR)/Makefile 36 | -------------------------------------------------------------------------------- /Examples/Ringmod/Ringmod.cpp: -------------------------------------------------------------------------------- 1 | /** Ringmode 2 | * 3 | * Demonstrates generating a signal, to create a basic audio effect 4 | * with multiple controls. 5 | * 6 | * A simple sine wave oscillator is used as the modulator, while the audio 7 | * input is used as the carrier. 8 | * 9 | * The following controls are used: 10 | * 11 | * - Warp: adjusts the frequency of modulation 12 | * - Blur: adjusts the depth of modulation 13 | * - Mix: adjusts the blend between the dry signal, and the ring-modulated signal 14 | */ 15 | #include "aurora.h" 16 | #include "daisysp.h" 17 | 18 | using namespace daisy; 19 | using namespace aurora; 20 | using namespace daisysp; 21 | 22 | Hardware hw; 23 | Oscillator osc; 24 | 25 | void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) 26 | { 27 | /** This filters, and prepares all of the module's controls for us. */ 28 | hw.ProcessAllControls(); 29 | 30 | /** Assign our knobs to some controls */ 31 | /** Modulator frequency in Hz */ 32 | float freq = fmap(hw.GetKnobValue(KNOB_WARP), 10.0, 1500.0, Mapping::LOG); 33 | osc.SetFreq(freq); 34 | 35 | /** How much modulation */ 36 | float depth = fmap(hw.GetKnobValue(KNOB_BLUR), 0.5, 1.0); 37 | 38 | /** Dry/Wet balance */ 39 | float mix = hw.GetKnobValue(KNOB_MIX); 40 | 41 | for (size_t i = 0; i < size; i++) 42 | { 43 | /** Read our inputs */ 44 | float dry_left = in[0][i]; 45 | float dry_right = in[1][i]; 46 | 47 | /** Generate the modulator signal */ 48 | float mod_signal = osc.Process() * depth; 49 | 50 | /** Create our ring modulated signals by modulating the two */ 51 | float wet_left = dry_left * mod_signal; 52 | float wet_right = dry_right * mod_signal; 53 | 54 | /** Now write the mix of the dry and wet together to the outputs */ 55 | /** Left */ 56 | out[0][i] = (dry_left * (1.f - mix)) + (wet_left * mix); 57 | /** Right */ 58 | out[1][i] = (dry_right * (1.f - mix)) + (wet_right * mix); 59 | } 60 | } 61 | 62 | int main(void) 63 | { 64 | /** Initialize the Hardware */ 65 | hw.Init(); 66 | 67 | /** Initialize the Oscillator we'll use to modulate our signal */ 68 | osc.Init(hw.AudioSampleRate()); 69 | osc.SetWaveform(Oscillator::WAVE_SIN); 70 | osc.SetAmp(1.0); 71 | 72 | 73 | /** Start the audio engine calling the function defined above periodically */ 74 | hw.StartAudio(AudioCallback); 75 | 76 | /** Infinite Loop */ 77 | while (1) 78 | { 79 | } 80 | } -------------------------------------------------------------------------------- /Examples/Volume/Makefile: -------------------------------------------------------------------------------- 1 | # Project Name 2 | TARGET = Volume 3 | 4 | # Build Project for Daisy Bootloader 5 | APP_TYPE = BOOT_SRAM 6 | 7 | # Sources 8 | CPP_SOURCES = Volume.cpp 9 | 10 | # Path to the root of the Aurora-SDK 11 | # When building custom applications outside of this repo 12 | # update this to point to the Aurora-SDK/ folder 13 | AURORA_SDK_PATH = ../.. 14 | 15 | # Location of Hardware Support File within the SDK 16 | C_INCLUDES += -I$(AURORA_SDK_PATH)/include/ 17 | 18 | # Library Locations 19 | LIBDAISY_DIR = $(AURORA_SDK_PATH)/libs/libDaisy/ 20 | DAISYSP_DIR = $(AURORA_SDK_PATH)/libs/DaisySP/ 21 | 22 | # To DEBUG the project with an ST-Link Probe: 23 | # 1. Compile the program with the below lines uncommented 24 | # 2. Load the firmware via the USB drive 25 | # 3. Make sure your .vscode/launch.json points to the 26 | # build/*.elf for the desired program 27 | # 4. Navigate and run the "Cortex Debug" Run and Debug configuration 28 | # or simply press F5 in VS Code. 29 | 30 | # DEBUG = 1 31 | # OPT = -O0 32 | 33 | # Core location, and generic Makefile. 34 | SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core 35 | include $(SYSTEM_FILES_DIR)/Makefile 36 | -------------------------------------------------------------------------------- /Examples/Volume/Volume.cpp: -------------------------------------------------------------------------------- 1 | /** Volume 2 | * 3 | * Demonstrates reading a knob to adjust the level of audio 4 | * passing through the module. 5 | * 6 | * The Mix knob is used to adjust the volume of the signal. 7 | */ 8 | #include "aurora.h" 9 | 10 | using namespace daisy; 11 | using namespace aurora; 12 | 13 | /** Our global hardware object */ 14 | Hardware hw; 15 | 16 | void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) 17 | { 18 | /** This filters, and prepares all of the module's controls for us. */ 19 | hw.ProcessAllControls(); 20 | 21 | /** Read the Mix knob value into a variable called "volume". */ 22 | float volume = hw.GetKnobValue(KNOB_MIX); 23 | 24 | /** Loop through each sample of audio */ 25 | for (size_t i = 0; i < size; i++) 26 | { 27 | /** Now for both left and right channels of audio, we'll multiply the input 28 | * by the volume to turn down the output signal. 29 | * 30 | * With Mix all the way down, this will result in silence. 31 | * With Mix all the way up, the audio should pass through at the normal level. 32 | */ 33 | 34 | /** Left Channel */ 35 | out[0][i] = in[0][i] * volume; 36 | 37 | /** Right Channel */ 38 | out[1][i] = in[1][i] * volume; 39 | } 40 | } 41 | 42 | int main(void) 43 | { 44 | /** Initialize the Hardware */ 45 | hw.Init(); 46 | 47 | /** Start the audio engine calling the function defined above periodically */ 48 | hw.StartAudio(AudioCallback); 49 | 50 | /** Infinite Loop */ 51 | while (1) 52 | { 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Build All](https://github.com/Qu-Bit-Electronix/Aurora-SDK/actions/workflows/main.yml/badge.svg)](https://github.com/Qu-Bit-Electronix/Aurora-SDK/actions/workflows/main.yml) 4 | [![Documentation](https://github.com/Qu-Bit-Electronix/Aurora-SDK/actions/workflows/docs.yml/badge.svg)](https://Qu-Bit-Electronix.github.io/Aurora-SDK) 5 | 6 | Everything you need to start writing your own application for the Qu-Bit Aurora Hardware platform! 7 | 8 | Check out the [reference documentation](https://qu-bit-electronix.github.io/Aurora-SDK/) for the project. 9 | 10 | Below we'll cover some basic topics to get started writing your own code to run on the Aurora 11 | 12 | # Table of Contents 13 | 14 | * [Compiling the Examples](#compiling-the-examples) 15 | * [Installing the Toolchain](#installing-the-toolchain) 16 | * [Cloning the SDK](#cloning-the-sdk) 17 | * [Building the Libraries](#building-the-libraries) 18 | * [Building the Examples](#building-the-examples) 19 | * [Reading and Running Examples](#reading-and-running-examples) 20 | * [Blink](#blink) 21 | * [Volume](#volume) 22 | * [Ringmod](#ringmod) 23 | * [Going Further](#going-further) 24 | * [Copying and Modifying an Example](#copying-and-modifying-an-example) 25 | * [Creating Your Own Firmware Project](#creating-your-own-firmware-project) 26 | * [Sharing Firmware With the Community](#sharing-firmware-with-the-community) 27 | 28 | # Compiling the Examples 29 | 30 | To begin, let's get the development environment set up to start working with your Aurora! 31 | 32 | Overall, this process is pretty simple, regardless of what operating system you're using. 33 | 34 | ## Installing the Toolchain 35 | 36 | The "toolchain" is a bundle of tools used to take the source code, and turn it into a binary file that can load onto the Aurora (or any other Daisy) hardware. 37 | 38 | If you've already done some development with Daisy in the past, you should already be good to go, and can skip ahead to the next section. 39 | 40 | On any operating system, our recommended text editor is VS Code, but you can work in any environment you're comfortable with. That said, we will only cover the specifics of working with VS Code in this guide. 41 | 42 | ### Windows 43 | 44 | 1. Download, and run the [Daisy Toolchain for Windows installer](https://media-obsy-dev.fra1.digitaloceanspaces.com/installers/DaisyToolchain-1.0.0-win64.exe) from Qu-bit website. 45 | 2. Download, and run the [Git for Windows installer](https://git-scm.com/download/win). 46 | 47 | This installs the required tools to get up and running with daisy. 48 | 49 | There are some additional helper scripts that require python, but this is optional. 50 | 51 | On Windows, you can install python by downloading the latest from [python.org](https://www.python.org/downloads/). 52 | 53 | **Note**: The windows store version of python will not work. 54 | 55 | ### Mac OS 56 | 57 | 1. Download the [Daisy Toolchain for Mac OS installer](https://media-obsy-dev.fra1.digitaloceanspaces.com/installers/DaisyToolchain-macos-installer-x64-0.1.2.pkg) from the Qu-Bit website. 58 | 2. Unzip, and Double click, the `install.command` file contained within. 59 | 60 | There are some additional helper scripts that require python, but this is optional. 61 | 62 | Mac OS comes with python, but the latest version can be downloaded from [python.org](https://www.python.org/downloads/), or using homebrew. 63 | 64 | If during the steps below, you run into an error similar to the following: 65 | 66 | ``` 67 | xcrun: error: invalid active developer path 68 | ``` 69 | 70 | then you need to run the following to update, or install the xcode developer tools: 71 | 72 | ``` 73 | xcode-select --install 74 | ``` 75 | 76 | ## Cloning the SDK 77 | 78 | 79 | 80 | The Aurora-SDK is a github repo, and it uses a few libraries to provide access to the hardware, and bundles the [DaisySP](https://github.com/electro-smith/DaisySP) DSP library. 81 | 82 | To download the entire SDK with all of its libraries: 83 | 84 | First, open a terminal, and navigate to the desired location on your computer. For our purposes here we'll use the `Desktop` folder, but you may wish to use `Documents`, `Developer`, or some other folder of your choice. 85 | 86 | Now, paste the following command into the terminal and press enter: 87 | 88 | ```shell 89 | git clone https://github.com/qu-bit-electronix/Aurora-SDK --recurse-submodules 90 | ``` 91 | 92 | Once its done you'll have an Aurora-SDK full of everything you need to start writing your own code. 93 | 94 | ## Building the Libraries 95 | 96 | The Aurora-SDK uses a few libraries to interface with the hardware, and provide a bunch of DSP that can be used within your projects. These need to be compiled before we can build any examples. 97 | 98 | The only time, other than after cloning the repo, that this needs to happen is when updating the libraries to a newer version. 99 | 100 | If you're using VS Code you can open the task menu by clicking: `Terminal->Run Task...` and selecting "Build Libraries" from the menu. 101 | 102 | This is the equivalent of running the following command in a terminal from the Aurora-SDK: 103 | 104 | ```shell 105 | ./ci/build_libs.sh 106 | ``` 107 | 108 | ## Building the Examples 109 | 110 | 111 | 112 | Each example is a single C++ file, and a `Makefile`. 113 | 114 | If you're using VS Code, you can build all examples by running the build task by clicking: `Terminal->Run Build Task...`. The shortcut for this is `CTRL+SHIFT+B` on Windows, or `CMD+SHIFT+B` on Mac OS. 115 | 116 | Alternatively, using the same task menu as above, you can build individual examples. For example, the task, `Build Example - Blink` will build the Blink example. 117 | 118 | This is the equivalent of running the following command in a terminal from the specific Example's folder: 119 | 120 | ```shell 121 | make 122 | ``` 123 | 124 | # Reading and Running Examples 125 | 126 | One of the best ways to see how stuff works is to try it out! 127 | 128 | In the `dist/Examples` folder is a precompiled version of each of the available examples, and the `Examples/` folder has the corresponding source code. 129 | 130 | Before we get into installing any tools or anything, let's take a moment to look over some of the basic examples, and see how to interact with the module hardware in C++ (it's easier than you might think)! 131 | 132 | To load one of the examples just drag the .bin file of your choice onto your USB drive, and power up the Aurora with it inserted. 133 | 134 | **Note**: In order to ensure that you're loading the desired program, you should have only one `.bin` file in the root directory of the USB drive. (you can keep as many as you want in folders, though!) 135 | 136 | The most up to date, official Aurora firmware can always be downloaded from [the Aurora product page](https://www.qubitelectronix.com/shop/aurora) 137 | 138 | ## Blink 139 | 140 | 141 | 142 | In this example, we're just periodically changing the state of an LED. No audio, no controls. 143 | 144 | Here, we can see a few things that will happen in every program: 145 | 146 | We always have a `Hardware` object, that's our connection to the module itself. Typically, we'll name it `hw` to keep it nice and short. 147 | 148 | We can use that to do things with the Hardware. For example, in this example, we change the state of an LED: 149 | 150 | ```cpp 151 | hw.SetLed(LED_FREEZE, 0.f, 0.f, led_state); 152 | ``` 153 | 154 | where the arguments are the LED we want to change followed by values for the red, green, and blue components of the light. 155 | 156 | Next, we toggle that state by changing the variable. 157 | 158 | Then we tell the hardware to write all changes to the LED values to the actual hardware: 159 | 160 | ```cpp 161 | hw.WriteLeds(); 162 | ``` 163 | 164 | Finally, we tell tell everything to wait 500 milliseconds (or 0.5 seconds) before looping back to the top of the infinite loop: 165 | 166 | ```cpp 167 | System::Delay(500); 168 | ``` 169 | 170 | Check out the [full example code here](https://github.com/Qu-Bit-Electronix/Aurora-SDK/blob/main/Examples/Blink/Blink.cpp) 171 | 172 | In more complex projects we'll want to avoid using delays, but we'll get into techniques for doing that later. 173 | 174 | ## Volume 175 | 176 | 177 | 178 | In this example, we're going to add two new things to what our previous example did: audio, and knobs! 179 | 180 | That's right, the most exciting eurorack concept ever invented -- a volume control. 181 | 182 | Just like in our last example, we're still going to create our `Hardware` object, and initialize it. 183 | 184 | However, now we're going to start up a new "callback" for handling audio. 185 | 186 | To define the the audio callback we write a function like this: 187 | 188 | ```cpp 189 | void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) 190 | { 191 | } 192 | ``` 193 | 194 | The function can have any name, but for clarity we'll simply call it `AudioCallback` within the example. 195 | 196 | The three arguments are: 197 | 198 | * in: real time stereo audio input from the hardware 199 | * out: real time stereo audio output from the hardware 200 | * size: the number of individual samples included in the buffers 201 | 202 | The audio default audio configuration is set up with an array of samples per channel, like this: 203 | 204 | ```cpp 205 | { 206 | { L0, L1, L2, . . ., LN }, 207 | { R0, R1, R2, . . ., RN } 208 | } 209 | ``` 210 | 211 | So using these arguments, we can loop over each sample of audio, individually. 212 | 213 | For example, just passing the input straight through to the output: 214 | 215 | ```cpp 216 | for (size_t i = 0; i < size; i++) 217 | { 218 | /** Left channel */ 219 | out[0][i] = in[0][i]; 220 | /** Right channel */ 221 | out[1][i] = in[1][i]; 222 | } 223 | ``` 224 | 225 | We want **control**, through. So what we want to do is use the value of one of the knobs to scale that level. 226 | 227 | Thie is pretty easy to setup. Within the callback, we'll want to process all of our controls, and then we can simply assign one of them to a variable called, "volume". 228 | 229 | Here we'll use the mix knob to get a 0-1 value. 230 | 231 | ```cpp 232 | void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) 233 | { 234 | hw.ProcessAllControls(); 235 | float volume = hw.GetKnobValue(KNOB_MIX); 236 | } 237 | ``` 238 | 239 | Knobs and CVs are handled separately to allow for more complex user interfaces, but there is an equivalent `GetCvValue` function that works in the same way. 240 | 241 | Within the `for` loop, we can scale the input by this amount to control the volume of our signal. 242 | 243 | In total, we now have the following Audio Callback: 244 | 245 | ```cpp 246 | void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) 247 | { 248 | hw.ProcessAllControls(); 249 | float volume = hw.GetKnobValue(KNOB_MIX); 250 | for (size_t i = 0; i < size; i++) 251 | { 252 | /** Left channel */ 253 | out[0][i] = in[0][i] * volume; 254 | /** Right channel */ 255 | out[1][i] = in[1][i] * volume; 256 | } 257 | } 258 | ``` 259 | 260 | For a bit more detail on how audio works on the Daisy, check out the [Getting Started - Audio](https://electro-smith.github.io/libDaisy/md_doc_md__a3__getting__started__audio.html) guide. 261 | 262 | And check out the [full example code here](https://github.com/Qu-Bit-Electronix/Aurora-SDK/blob/main/Examples/Volume/Volume.cpp) 263 | 264 | 265 | ## Ringmod 266 | 267 | 268 | 269 | In this example, we're going to take what we learned in the Volume example, and make something a bit more exciting happen. 270 | 271 | A ring modulator is a form of bipolar amplitude modulation that can be easily made by multiplying one signal (like our input signal), and another signal. 272 | 273 | In this example we're going to break out a few things from the DaisySP DSP library. 274 | 275 | We'll also be using the `fmap` utility function to help us scale our control's 0-1 values to a more desirable range for our controls. 276 | 277 | For example, when setting our modulator frequency we'll want to have a logarthmic curve, and a frequency range from say, 10Hz to 1.5kHz. 278 | Using `fmap` we can save ourselves from figuring out how to do that with math. 279 | 280 | The fmap function take up to 4 arguments: 281 | 282 | 1. The 0-1 input to be converted (here we use `hw.GetKnobValue(KNOB_WARP)`) 283 | 2. The new minimum 284 | 3. The new maximum 285 | 4. The curve for the mapping function. If this isn't specified, it defaults to a normal, linear curve. 286 | 287 | ```cpp 288 | float freq = fmap(hw.GetKnobValue(KNOB_WARP), 10.0, 1500.0, Mapping::LOG); 289 | ``` 290 | 291 | We'll also be using an `Oscillator` to generate a sine wave for our modulator. 292 | 293 | The `Oscillator`, like nearly every other DaisySP object, has two core functions: `Init` and `Process`. 294 | 295 | We want to initialize the oscillator, and set any particular parameters after initializing our Hardware object: 296 | 297 | ```cpp 298 | osc.Init(hw.AudioSampleRate()); 299 | osc.SetWaveform(Oscillator::WAVE_SIN); 300 | osc.SetAmp(1.0); 301 | ``` 302 | 303 | And then within our per-sample loop of the audio callback, we can render the oscillator's signal by calling it's `Process` function. 304 | 305 | ```cpp 306 | for (size_t i = 0; i < size; i++) { 307 | float signal = osc.Process(); 308 | } 309 | ``` 310 | 311 | To do the actual ring modulation effect, we'll multiply that signal by our input signal. 312 | 313 | Check out the [full example code here](https://github.com/Qu-Bit-Electronix/Aurora-SDK/blob/main/Examples/Ringmod/Ringmod.cpp) 314 | 315 | # Going Further 316 | 317 | Now that we can compile the provided examples, let's look at where we might want to go from here. 318 | 319 | ## Copying and Modifying an Example 320 | 321 | A great starting point is to copy one of the existing examples, and making modifications. 322 | 323 | For example, a good first modification to any of the existing examples would be to add the corresponding CV input to one of the knob controls. 324 | 325 | Since the examples are only a pair of files each, all you really need to do is Copy/Paste the folder and change the name. 326 | 327 | In the `Makefile` you'll want to: 328 | 329 | * update the `TARGET` name (on line 2) 330 | * and the `CPP_SOURCES` filename (on line 8) if you change the cpp file name. 331 | 332 | To build your new file you'll either need to edit the `.vscode/tasks.json` file (copying the entry for "Build Example - X" to your new folder name), or to run the `make` command directly from the terminal 333 | 334 | ## Creating Your Own Firmware Project 335 | 336 | Once you've dabbled a bit, and want to make your own custom firmware for yourself (or to share with the community), you can set some stuff up to start making your own folders. 337 | 338 | If you're keeping your new project in the `Examples` folder you won't have to change much other than what was mentioned above. 339 | 340 | However, if you want to have a dedicated folder (perhaps a github repo of your own) to keep your project, you may want to have your source files _outside_ of the Aurora-SDK folder. 341 | 342 | In this situation, the only other thing you'll want to update is the `AURORA_SDK` path (line 13) of the Makefile to point to a copy of the Aurora-SDK somewhere on your comptuer. This path can be relative, or absolute. 343 | 344 | If you're making you're making your project a github repo. You can add the SDK to your own repo as a submodule using: 345 | 346 | ```shell 347 | git submodule add https://github.com/qu-bit-electronix/Aurora-SDK 348 | ``` 349 | 350 | and initialize it with: 351 | 352 | ```shell 353 | git submodule update --init 354 | ``` 355 | 356 | ## Sharing Firmware With the Community 357 | 358 | Once you've got something cool you may want to share the binary, and/or the source code with the community! 359 | 360 | The [Qu-Bit Discord](https://discord.gg/BZmuBxcE) is a great place to share your project along with any details about how it works. 361 | 362 | Another great place to share your project is on [patchstorage](patchstorage.com). 363 | 364 | [](https://discord.gg/BZmuBxcE) 365 | -------------------------------------------------------------------------------- /ci/build_dist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Scans list of directories provided for examples containing a binary file, 4 | # and compiles a dist/ folder containing a matching hierarchy of binary files 5 | # and some metadata associated with them. 6 | # 7 | # This is primarily for providing easy access to pre-compiled examples for the compiler 8 | # Web Programmer application 9 | # 10 | # Examples must be compiled locally (or in the cloud service running this), otherwise 11 | # there won't be any binary files to grab from, and the script will skip over everything. 12 | # 13 | ############################## 14 | # Script Begin ############### 15 | ############################## 16 | import argparse 17 | import os 18 | import shutil 19 | import json 20 | import glob 21 | 22 | 23 | class Example(object): 24 | def __init__(self, name, ogdir): 25 | self.name = name 26 | self.description = "no desc available yet" 27 | self.url = 'https://raw.githubusercontent.com/electro-smith/' 28 | self.platform = ogdir.replace(os.path.sep, '_') 29 | self.url += 'DaisyExamples/master/' + ogdir.replace(os.path.sep, '/') + '/' + self.name + '/README.md' 30 | self.apath = os.path.abspath('/'.join((ogdir, name))) 31 | flist = glob.glob('{}/build/*.bin'.format(self.apath)) 32 | if len(flist) > 0: 33 | self.buildpath = flist[0] 34 | else: 35 | self.buildpath = None 36 | self.destpath = './dist/{}/{}.bin'.format(self.platform, self.name) 37 | 38 | def Valid(self): 39 | if self.buildpath is not None: 40 | return True 41 | else: 42 | return False 43 | 44 | # packs necessary data and returns json object 45 | def DumpObject(self): 46 | myobj = {} 47 | myobj['name'] = self.name 48 | myobj['platform'] = self.platform 49 | myobj['filepath'] = self.destpath 50 | myobj['description'] = self.description 51 | myobj['url'] = self.url 52 | return myobj 53 | 54 | def DumpJson(self, filepointer): 55 | myobj = self.DumpObject() 56 | return json.dump(myobj, filepointer) 57 | 58 | def CopyToDeploy(self): 59 | if not os.path.isdir('./dist'): 60 | os.mkdir('./dist') 61 | if not os.path.isdir(os.path.dirname(self.destpath)): 62 | os.mkdir(os.path.dirname(self.destpath)) 63 | if self.buildpath is not None: 64 | shutil.copy(self.buildpath, self.destpath) 65 | 66 | 67 | def run(): 68 | # Parse arguments 69 | parser = argparse.ArgumentParser( 70 | description='generates the dist/ directory, containing binaries for all examples, and a json file containing simple metadata for each example.') 71 | parser.add_argument('directory_list', nargs='*', 72 | help='list of directories separated by spaces to use as inputs for the dist folder') 73 | parser.add_argument('-e', '--exclude_list', nargs='*', 74 | help='list of directories separated by spaces to ignore searching') 75 | parser.add_argument('-r', '--rewrite', action='store_true', 76 | help='When set, this will cause the script to completely clear the dist/ directory before executing.') 77 | parser.add_argument('-u', '--human-readable', action='store_true', 78 | help='When set, this will use indentation in the json output. By default the output will be a single line of text.') 79 | args = parser.parse_args() 80 | 81 | if not args.exclude_list: 82 | filter_dirs = ["libDaisy", 83 | "DaisySP", 84 | ".github", 85 | ".vscode", 86 | ".git", 87 | "ci", 88 | "cube", 89 | "dist", 90 | "utils", 91 | "stmlib", 92 | "libdaisy", 93 | "DaisyToolchain", 94 | "MyProjects"] 95 | else: 96 | filter_dirs = args.exclude_list 97 | 98 | # Prior to novemenber 2021 this would be a default-list, and was required. 99 | if not args.directory_list: 100 | # directories = [ 'seed', 'pod', 'patch', 'field', 'petal', 'versio', 'patch_sm' ] 101 | directories = list( 102 | filter(lambda x: x not in filter_dirs and os.path.isdir(x), os.listdir('.'))) 103 | else: 104 | directories = list(args.directory_list) 105 | 106 | # navigate directories, and create examples to add to JSON, and dist/ 107 | olist = [] 108 | for d in directories: 109 | for root, dirs, files in os.walk(d): 110 | if ('Makefile') in files: 111 | ex_name = os.path.basename(root) 112 | ex_path = os.path.normpath(os.path.join(root, '..')) 113 | newobj = Example(ex_name, ex_path) 114 | olist.append(newobj) 115 | 116 | if args.rewrite and os.path.isdir('./dist'): 117 | shutil.rmtree('./dist') 118 | 119 | # Creating New Build Dir 120 | for example in olist: 121 | example.CopyToDeploy() 122 | jsonout = list(example.DumpObject() for example in olist if example.Valid()) 123 | 124 | # Creating JSON file 125 | with open('./dist/examples.json', 'w') as f: 126 | if args.human_readable: 127 | json.dump(jsonout, f, indent=4) 128 | else: 129 | json.dump(jsonout, f) 130 | 131 | if __name__ == '__main__': 132 | run() 133 | 134 | -------------------------------------------------------------------------------- /ci/build_examples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # recompiles all Make-based projects within the repository 4 | # excluding any that are located within the specified excluded 5 | # directory list 6 | # 7 | import sys 8 | import os 9 | 10 | filter_dirs = ["libDaisy", 11 | "DaisySP", 12 | ".github", 13 | ".vscode", 14 | ".git", 15 | "ci", 16 | "cube", 17 | "dist", 18 | "utils", 19 | "stmlib", 20 | "libdaisy", 21 | "DaisyToolchain", 22 | "libs", 23 | "MyProjects"] 24 | 25 | dirs_to_search = list( 26 | filter(lambda x: x not in filter_dirs and os.path.isdir(x), os.listdir('.'))) 27 | 28 | # recursively go through each directory in dirs_to_search 29 | # and attempt to compile each example 30 | for dir in dirs_to_search: 31 | example_dirs = [] 32 | for root, dirs, files in os.walk(dir): 33 | if 'Makefile' in files: 34 | example_dirs.append(root) 35 | cwd = os.path.abspath(os.getcwd()) 36 | for ex in example_dirs: 37 | dest = os.path.join(cwd, ex) 38 | os.chdir(dest) 39 | os.system("echo Building: {}".format(ex)) 40 | exit_code = os.system('make -s clean') 41 | exit_code = os.system('make -s') 42 | if exit_code != 0: 43 | os.chdir(cwd) 44 | sys.exit(1) 45 | os.chdir(cwd) 46 | # exit successfully 47 | print("done") 48 | sys.exit(0) 49 | -------------------------------------------------------------------------------- /ci/build_libs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | START_DIR=$PWD 4 | LIBDAISY_DIR=$PWD/libs/libDaisy 5 | DAISYSP_DIR=$PWD/libs/DaisySP 6 | 7 | echo "building libDaisy . . ." 8 | cd "$LIBDAISY_DIR" ; make -s clean ; make -j -s 9 | if [ $? -ne 0 ] 10 | then 11 | echo "Failed to compile libDaisy" 12 | exit 1 13 | fi 14 | echo "done." 15 | 16 | echo "building DaisySP . . ." 17 | cd "$DAISYSP_DIR" ; make -s clean ; make -j -s 18 | if [ $? -ne 0 ] 19 | then 20 | echo "Failed to compile DaisySP" 21 | exit 1 22 | fi 23 | echo "done." 24 | 25 | -------------------------------------------------------------------------------- /ci/copy_readme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p temp_docs 4 | cp README.md temp_docs/ -------------------------------------------------------------------------------- /ci/local_style_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO: 4 | # having to do a separate line for each exclude is silly 5 | # and we should be able to run this with wildcards without it 6 | # going crazy. 7 | 8 | python ./utils/run-clang-format.py -r ./ \ 9 | -e ./DaisySP -e ./libdaisy -e ./cube -e ./utils -e ./resources \ 10 | -e ./seed/experimental -e ./patch/experimental \ 11 | --extensions c,cpp,h 12 | -------------------------------------------------------------------------------- /ci/rm_readme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf ./temp_docs -------------------------------------------------------------------------------- /dist/Examples/Blink.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qu-Bit-Electronix/Aurora-SDK/8b0378bd8f54d770a329a57250dcedb1f68fcbaa/dist/Examples/Blink.bin -------------------------------------------------------------------------------- /dist/Examples/Ringmod.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qu-Bit-Electronix/Aurora-SDK/8b0378bd8f54d770a329a57250dcedb1f68fcbaa/dist/Examples/Ringmod.bin -------------------------------------------------------------------------------- /dist/Examples/Volume.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qu-Bit-Electronix/Aurora-SDK/8b0378bd8f54d770a329a57250dcedb1f68fcbaa/dist/Examples/Volume.bin -------------------------------------------------------------------------------- /dist/examples.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Ringmod", 4 | "platform": "Examples", 5 | "filepath": "./dist/Examples/Ringmod.bin", 6 | "description": "no desc available yet", 7 | "url": "https://raw.githubusercontent.com/electro-smith/DaisyExamples/master/Examples/Ringmod/README.md" 8 | }, 9 | { 10 | "name": "Blink", 11 | "platform": "Examples", 12 | "filepath": "./dist/Examples/Blink.bin", 13 | "description": "no desc available yet", 14 | "url": "https://raw.githubusercontent.com/electro-smith/DaisyExamples/master/Examples/Blink/README.md" 15 | }, 16 | { 17 | "name": "Volume", 18 | "platform": "Examples", 19 | "filepath": "./dist/Examples/Volume.bin", 20 | "description": "no desc available yet", 21 | "url": "https://raw.githubusercontent.com/electro-smith/DaisyExamples/master/Examples/Volume/README.md" 22 | } 23 | ] -------------------------------------------------------------------------------- /docs/DoxygenLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | -------------------------------------------------------------------------------- /docs/extra/doxygen-awesome-darkmode-toggle.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2021 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | class DoxygenAwesomeDarkModeToggle extends HTMLElement { 31 | static prefersLightModeInDarkModeKey = "prefers-light-mode-in-dark-mode" 32 | static prefersDarkModeInLightModeKey = "prefers-dark-mode-in-light-mode" 33 | 34 | static _staticConstructor = function() { 35 | DoxygenAwesomeDarkModeToggle.darkModeEnabled = DoxygenAwesomeDarkModeToggle.userPreference 36 | DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled) 37 | // Update the color scheme when the browsers preference changes 38 | // without user interaction on the website. 39 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { 40 | DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged() 41 | }) 42 | // Update the color scheme when the tab is made visible again. 43 | // It is possible that the appearance was changed in another tab 44 | // while this tab was in the background. 45 | document.addEventListener("visibilitychange", visibilityState => { 46 | if (document.visibilityState === 'visible') { 47 | DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged() 48 | } 49 | }); 50 | }() 51 | 52 | constructor() { 53 | super(); 54 | this.onclick=this.toggleDarkMode 55 | } 56 | 57 | /** 58 | * @returns `true` for dark-mode, `false` for light-mode system preference 59 | */ 60 | static get systemPreference() { 61 | return window.matchMedia('(prefers-color-scheme: dark)').matches 62 | } 63 | 64 | /** 65 | * @returns `true` for dark-mode, `false` for light-mode user preference 66 | */ 67 | static get userPreference() { 68 | return (!DoxygenAwesomeDarkModeToggle.systemPreference && localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)) || 69 | (DoxygenAwesomeDarkModeToggle.systemPreference && !localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey)) 70 | } 71 | 72 | static set userPreference(userPreference) { 73 | DoxygenAwesomeDarkModeToggle.darkModeEnabled = userPreference 74 | if(!userPreference) { 75 | if(DoxygenAwesomeDarkModeToggle.systemPreference) { 76 | localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey, true) 77 | } else { 78 | localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey) 79 | } 80 | } else { 81 | if(!DoxygenAwesomeDarkModeToggle.systemPreference) { 82 | localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey, true) 83 | } else { 84 | localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey) 85 | } 86 | } 87 | DoxygenAwesomeDarkModeToggle.onUserPreferenceChanged() 88 | } 89 | 90 | static enableDarkMode(enable) { 91 | let head = document.getElementsByTagName('head')[0] 92 | if(enable) { 93 | document.documentElement.classList.add("dark-mode") 94 | document.documentElement.classList.remove("light-mode") 95 | } else { 96 | document.documentElement.classList.remove("dark-mode") 97 | document.documentElement.classList.add("light-mode") 98 | } 99 | } 100 | 101 | static onSystemPreferenceChanged() { 102 | DoxygenAwesomeDarkModeToggle.darkModeEnabled = DoxygenAwesomeDarkModeToggle.userPreference 103 | DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled) 104 | } 105 | 106 | static onUserPreferenceChanged() { 107 | DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled) 108 | } 109 | 110 | toggleDarkMode() { 111 | DoxygenAwesomeDarkModeToggle.userPreference = !DoxygenAwesomeDarkModeToggle.userPreference 112 | } 113 | } 114 | 115 | customElements.define("doxygen-awesome-dark-mode-toggle", DoxygenAwesomeDarkModeToggle); 116 | -------------------------------------------------------------------------------- /docs/extra/doxygen-awesome-sidebar-only-darkmode-toggle.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | 4 | Doxygen Awesome 5 | https://github.com/jothepro/doxygen-awesome-css 6 | 7 | MIT License 8 | 9 | Copyright (c) 2021 jothepro 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | 29 | */ 30 | 31 | @media screen and (min-width: 768px) { 32 | 33 | #MSearchBox { 34 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px); 35 | } 36 | 37 | #MSearchField { 38 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/extra/doxygen-awesome-sidebar-only.css: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2021 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | html { 31 | /* side nav width. MUST be = `TREEVIEW_WIDTH`. 32 | * Make sure it is wide enought to contain the page title (logo + title + version) 33 | */ 34 | --side-nav-fixed-width: 340px; 35 | --menu-display: none; 36 | 37 | --top-height: 120px; 38 | } 39 | 40 | 41 | @media screen and (min-width: 768px) { 42 | html { 43 | --searchbar-background: var(--page-background-color); 44 | } 45 | 46 | #side-nav { 47 | min-width: var(--side-nav-fixed-width); 48 | max-width: var(--side-nav-fixed-width); 49 | top: var(--top-height); 50 | overflow: visible; 51 | } 52 | 53 | #nav-tree, #side-nav { 54 | height: calc(100vh - var(--top-height)) !important; 55 | } 56 | 57 | #nav-tree { 58 | padding: 0; 59 | } 60 | 61 | #top { 62 | display: block; 63 | border-bottom: none; 64 | height: var(--top-height); 65 | margin-bottom: calc(0px - var(--top-height)); 66 | max-width: var(--side-nav-fixed-width); 67 | background: var(--side-nav-background); 68 | } 69 | #main-nav { 70 | float: left; 71 | padding-right: 0; 72 | } 73 | 74 | .ui-resizable-handle { 75 | cursor: default; 76 | width: 1px !important; 77 | box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color); 78 | } 79 | 80 | #nav-path { 81 | position: fixed; 82 | right: 0; 83 | left: var(--side-nav-fixed-width); 84 | bottom: 0; 85 | width: auto; 86 | } 87 | 88 | #doc-content { 89 | height: calc(100vh - 31px) !important; 90 | padding-bottom: calc(3 * var(--spacing-large)); 91 | padding-top: calc(var(--top-height) - 80px); 92 | box-sizing: border-box; 93 | margin-left: var(--side-nav-fixed-width) !important; 94 | } 95 | 96 | #MSearchBox { 97 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium))); 98 | } 99 | 100 | #MSearchField { 101 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px); 102 | } 103 | 104 | #MSearchResultsWindow { 105 | left: var(--spacing-medium) !important; 106 | right: auto; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /docs/extra/doxygen-awesome.css: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2021 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | html { 31 | /* primary theme color. This will affect the entire websites color scheme: links, arrows, labels, ... */ 32 | --primary-color: #1779c4; 33 | --primary-dark-color: #00559f; 34 | --primary-light-color: #7aabd6; 35 | --primary-lighter-color: #cae1f1; 36 | --primary-lightest-color: #e9f1f8; 37 | 38 | /* page base colors */ 39 | --page-background-color: white; 40 | --page-foreground-color: #2c3e50; 41 | --page-secondary-foreground-color: #67727e; 42 | 43 | /* color for all separators on the website: hr, borders, ... */ 44 | --separator-color: #dedede; 45 | 46 | /* border radius for all rounded components. Will affect many components, like dropdowns, memitems, codeblocks, ... */ 47 | --border-radius-large: 8px; 48 | --border-radius-small: 4px; 49 | --border-radius-medium: 6px; 50 | 51 | /* default spacings. Most compontest reference these values for spacing, to provide uniform spacing on the page. */ 52 | --spacing-small: 5px; 53 | --spacing-medium: 10px; 54 | --spacing-large: 16px; 55 | 56 | /* default box shadow used for raising an element above the normal content. Used in dropdowns, Searchresult, ... */ 57 | --box-shadow: 0 2px 10px 0 rgba(0,0,0,.1); 58 | 59 | --odd-color: rgba(0,0,0,.03); 60 | 61 | /* font-families. will affect all text on the website 62 | * font-family: the normal font for text, headlines, menus 63 | * font-family-monospace: used for preformatted text in memtitle, code, fragments 64 | */ 65 | --font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; 66 | --font-family-monospace: source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace; 67 | 68 | /* font sizes */ 69 | --page-font-size: 15.6px; 70 | --navigation-font-size: 14.4px; 71 | --code-font-size: 14.4px; /* affects code, fragment */ 72 | --title-font-size: 22px; 73 | 74 | /* content text properties. These only affect the page content, not the navigation or any other ui elements */ 75 | --content-line-height: 27px; 76 | /* The content is centered and constraint in it's width. To make the content fill the whole page, set the variable to auto.*/ 77 | --content-maxwidth: 1000px; 78 | 79 | /* colors for various content boxes: @warning, @note, @deprecated @bug */ 80 | --warning-color: #fca49b; 81 | --warning-color-dark: #b61825; 82 | --warning-color-darker: #75070f; 83 | --note-color: rgba(255,229,100,.3); 84 | --note-color-dark: #c39900; 85 | --note-color-darker: #8d7400; 86 | --deprecated-color: rgb(214, 216, 224); 87 | --deprecated-color-dark: #5b6269; 88 | --deprecated-color-darker: #43454a; 89 | --bug-color: rgb(246, 208, 178); 90 | --bug-color-dark: #a53a00; 91 | --bug-color-darker: #5b1d00; 92 | --invariant-color: #b7f8d0; 93 | --invariant-color-dark: #00ba44; 94 | --invariant-color-darker: #008622; 95 | 96 | /* blockquote colors */ 97 | --blockquote-background: #f5f5f5; 98 | --blockquote-foreground: #727272; 99 | 100 | /* table colors */ 101 | --tablehead-background: #f1f1f1; 102 | --tablehead-foreground: var(--page-foreground-color); 103 | 104 | /* menu-display: block | none 105 | * Visibility of the top navigation on screens >= 768px. On smaller screen the menu is always visible. 106 | * `GENERATE_TREEVIEW` MUST be enabled! 107 | */ 108 | --menu-display: block; 109 | 110 | --menu-focus-foreground: var(--page-background-color); 111 | --menu-focus-background: var(--primary-color); 112 | --menu-selected-background: rgba(0,0,0,.05); 113 | 114 | 115 | --header-background: var(--page-background-color); 116 | --header-foreground: var(--page-foreground-color); 117 | 118 | /* searchbar colors */ 119 | --searchbar-background: var(--side-nav-background); 120 | --searchbar-foreground: var(--page-foreground-color); 121 | 122 | /* searchbar size 123 | * (`searchbar-width` is only applied on screens >= 768px. 124 | * on smaller screens the searchbar will always fill the entire screen width) */ 125 | --searchbar-height: 33px; 126 | --searchbar-width: 210px; 127 | 128 | /* code block colors */ 129 | --code-background: #f5f5f5; 130 | --code-foreground: var(--page-foreground-color); 131 | 132 | /* fragment colors */ 133 | --fragment-background: #282c34; 134 | --fragment-foreground: #ffffff; 135 | --fragment-keyword: #cc99cd; 136 | --fragment-keywordtype: #ab99cd; 137 | --fragment-keywordflow: #e08000; 138 | --fragment-token: #7ec699; 139 | --fragment-comment: #999999; 140 | --fragment-link: #98c0e3; 141 | --fragment-preprocessor: #65cabe; 142 | --fragment-linenumber-color: #cccccc; 143 | --fragment-linenumber-background: #35393c; 144 | --fragment-linenumber-border: #1f1f1f; 145 | --fragment-lineheight: 20px; 146 | 147 | /* sidebar navigation (treeview) colors */ 148 | --side-nav-background: #fbfbfb; 149 | --side-nav-foreground: var(--page-foreground-color); 150 | --side-nav-arrow-opacity: 0.6; 151 | --side-nav-arrow-hover-opacity: 0.9; 152 | 153 | /* height of an item in any tree / collapsable table */ 154 | --tree-item-height: 30px; 155 | 156 | --darkmode-toggle-button-icon: '☀️' 157 | } 158 | 159 | @media screen and (max-width: 767px) { 160 | html { 161 | --page-font-size: 16px; 162 | --navigation-font-size: 16px; 163 | --code-font-size: 15px; /* affects code, fragment */ 164 | --title-font-size: 22px; 165 | } 166 | } 167 | 168 | @media (prefers-color-scheme: dark) { 169 | html:not(.light-mode) { 170 | color-scheme: dark; 171 | 172 | --primary-color: #1982d2; 173 | --primary-dark-color: #5ca8e2; 174 | --primary-light-color: #4779ac; 175 | --primary-lighter-color: #191e21; 176 | --primary-lightest-color: #191a1c; 177 | 178 | --box-shadow: 0 2px 10px 0 rgba(0,0,0,.35); 179 | 180 | --odd-color: rgba(0,0,0,.1); 181 | 182 | --menu-selected-background: rgba(0,0,0,.4); 183 | 184 | --page-background-color: #1C1D1F; 185 | --page-foreground-color: #d2dbde; 186 | --page-secondary-foreground-color: #859399; 187 | --separator-color: #000000; 188 | --side-nav-background: #252628; 189 | 190 | --code-background: #2a2c2f; 191 | 192 | --tablehead-background: #2a2c2f; 193 | 194 | --blockquote-background: #1f2022; 195 | --blockquote-foreground: #77848a; 196 | 197 | --warning-color: #b61825; 198 | --warning-color-dark: #510a02; 199 | --warning-color-darker: #f5b1aa; 200 | --note-color: rgb(255, 183, 0); 201 | --note-color-dark: #9f7300; 202 | --note-color-darker: #645b39; 203 | --deprecated-color: rgb(88, 90, 96); 204 | --deprecated-color-dark: #262e37; 205 | --deprecated-color-darker: #a0a5b0; 206 | --bug-color: rgb(248, 113, 0); 207 | --bug-color-dark: #812a00; 208 | --bug-color-darker: #ffd3be; 209 | 210 | --darkmode-toggle-button-icon: '🌛'; 211 | } 212 | } 213 | 214 | /* dark mode variables are defined twice, to support both the dark-mode without and with doxygen-awesome-darkmode-toggle.js */ 215 | html.dark-mode { 216 | color-scheme: dark; 217 | 218 | --primary-color: #1982d2; 219 | --primary-dark-color: #5ca8e2; 220 | --primary-light-color: #4779ac; 221 | --primary-lighter-color: #191e21; 222 | --primary-lightest-color: #191a1c; 223 | 224 | --box-shadow: 0 2px 10px 0 rgba(0,0,0,.35); 225 | 226 | --odd-color: rgba(0,0,0,.1); 227 | 228 | --menu-selected-background: rgba(0,0,0,.4); 229 | 230 | --page-background-color: #1C1D1F; 231 | --page-foreground-color: #d2dbde; 232 | --page-secondary-foreground-color: #859399; 233 | --separator-color: #000000; 234 | --side-nav-background: #252628; 235 | 236 | --code-background: #2a2c2f; 237 | 238 | --tablehead-background: #2a2c2f; 239 | 240 | --blockquote-background: #1f2022; 241 | --blockquote-foreground: #77848a; 242 | 243 | --warning-color: #b61825; 244 | --warning-color-dark: #510a02; 245 | --warning-color-darker: #f5b1aa; 246 | --note-color: rgb(255, 183, 0); 247 | --note-color-dark: #9f7300; 248 | --note-color-darker: #645b39; 249 | --deprecated-color: rgb(88, 90, 96); 250 | --deprecated-color-dark: #262e37; 251 | --deprecated-color-darker: #a0a5b0; 252 | --bug-color: rgb(248, 113, 0); 253 | --bug-color-dark: #812a00; 254 | --bug-color-darker: #ffd3be; 255 | 256 | --darkmode-toggle-button-icon: '🌛'; 257 | } 258 | 259 | body { 260 | color: var(--page-foreground-color); 261 | background-color: var(--page-background-color); 262 | font-size: var(--page-font-size); 263 | } 264 | 265 | body, table, div, p, dl, #nav-tree .label, .title, .sm-dox a, .sm-dox a:hover, .sm-dox a:focus, #projectname, .SelectItem, #MSearchField, .navpath li.navelem a, .navpath li.navelem a:hover { 266 | font-family: var(--font-family); 267 | } 268 | 269 | h1, h2, h3, h4, h5 { 270 | margin-top: .9em; 271 | font-weight: 600; 272 | line-height: initial; 273 | } 274 | 275 | p, div, table, dl { 276 | font-size: var(--page-font-size); 277 | } 278 | 279 | a:link, a:visited, a:hover, a:focus, a:active { 280 | color: var(--primary-color) !important; 281 | font-weight: 500; 282 | } 283 | 284 | /* 285 | Title and top navigation 286 | */ 287 | 288 | #top { 289 | background: var(--header-background); 290 | border-bottom: 1px solid var(--separator-color); 291 | } 292 | 293 | @media screen and (min-width: 768px) { 294 | #top { 295 | display: flex; 296 | flex-wrap: wrap; 297 | justify-content: space-between; 298 | align-items: center; 299 | } 300 | } 301 | 302 | #main-nav { 303 | flex-grow: 5; 304 | padding: var(--spacing-small) var(--spacing-medium); 305 | } 306 | 307 | #titlearea { 308 | width: auto; 309 | padding: var(--spacing-medium) var(--spacing-large); 310 | background: none; 311 | color: var(--header-foreground); 312 | border-bottom: none; 313 | } 314 | 315 | @media screen and (max-width: 767px) { 316 | #titlearea { 317 | padding-bottom: var(--spacing-small); 318 | } 319 | } 320 | 321 | #titlearea table tbody tr { 322 | height: auto !important; 323 | } 324 | 325 | #projectname { 326 | font-size: var(--title-font-size); 327 | font-weight: 600; 328 | } 329 | 330 | #projectnumber { 331 | font-family: inherit; 332 | font-size: 60%; 333 | } 334 | 335 | #projectbrief { 336 | font-family: inherit; 337 | font-size: 80%; 338 | } 339 | 340 | #projectlogo { 341 | vertical-align: middle; 342 | } 343 | 344 | #projectlogo img { 345 | max-height: calc(var(--title-font-size) * 2); 346 | margin-right: var(--spacing-small); 347 | } 348 | 349 | .sm-dox, .tabs, .tabs2, .tabs3 { 350 | background: none; 351 | padding: 0; 352 | } 353 | 354 | .tabs, .tabs2, .tabs3 { 355 | border-bottom: 1px solid var(--separator-color); 356 | margin-bottom: -1px; 357 | } 358 | 359 | @media screen and (max-width: 767px) { 360 | .sm-dox a span.sub-arrow { 361 | background: var(--code-background); 362 | } 363 | } 364 | 365 | @media screen and (min-width: 768px) { 366 | .sm-dox li, .tablist li { 367 | display: var(--menu-display); 368 | } 369 | 370 | .sm-dox a span.sub-arrow { 371 | border-color: var(--header-foreground) transparent transparent transparent; 372 | } 373 | 374 | .sm-dox a:hover span.sub-arrow { 375 | border-color: var(--menu-focus-foreground) transparent transparent transparent; 376 | } 377 | 378 | .sm-dox ul a span.sub-arrow { 379 | border-color: transparent transparent transparent var(--page-foreground-color); 380 | } 381 | 382 | .sm-dox ul a:hover span.sub-arrow { 383 | border-color: transparent transparent transparent var(--menu-focus-foreground); 384 | } 385 | } 386 | 387 | .sm-dox ul { 388 | background: var(--page-background-color); 389 | box-shadow: var(--box-shadow); 390 | border: 1px solid var(--separator-color); 391 | border-radius: var(--border-radius-medium) !important; 392 | padding: var(--spacing-small); 393 | animation: ease-out 150ms slideInMenu; 394 | } 395 | 396 | @keyframes slideInMenu { 397 | from { 398 | opacity: 0; 399 | transform: translate(0px, -2px); 400 | } 401 | 402 | to { 403 | opacity: 1; 404 | transform: translate(0px, 0px); 405 | } 406 | } 407 | 408 | .sm-dox ul a { 409 | color: var(--page-foreground-color) !important; 410 | background: var(--page-background-color); 411 | font-size: var(--navigation-font-size); 412 | } 413 | 414 | .sm-dox>li>ul:after { 415 | border-bottom-color: var(--page-background-color) !important; 416 | } 417 | 418 | .sm-dox>li>ul:before { 419 | border-bottom-color: var(--separator-color) !important; 420 | } 421 | 422 | .sm-dox ul a:hover, .sm-dox ul a:active, .sm-dox ul a:focus { 423 | font-size: var(--navigation-font-size) !important; 424 | color: var(--menu-focus-foreground) !important; 425 | text-shadow: none; 426 | background-color: var(--menu-focus-background); 427 | border-radius: var(--border-radius-small) !important; 428 | } 429 | 430 | .sm-dox a, .sm-dox a:focus, .tablist li, .tablist li a, .tablist li.current a { 431 | text-shadow: none; 432 | background: transparent; 433 | background-image: none !important; 434 | color: var(--header-foreground) !important; 435 | font-weight: normal; 436 | font-size: var(--navigation-font-size); 437 | } 438 | 439 | .sm-dox a:focus { 440 | outline: auto; 441 | } 442 | 443 | .sm-dox a:hover, .sm-dox a:active, .tablist li a:hover { 444 | text-shadow: none; 445 | font-weight: normal; 446 | background: var(--menu-focus-background); 447 | color: var(--menu-focus-foreground) !important; 448 | border-radius: var(--border-radius-small) !important; 449 | font-size: var(--navigation-font-size); 450 | } 451 | 452 | .tablist li.current { 453 | border-radius: var(--border-radius-small); 454 | background: var(--menu-selected-background); 455 | } 456 | 457 | .tablist li { 458 | margin: var(--spacing-small) 0 var(--spacing-small) var(--spacing-small); 459 | } 460 | 461 | .tablist a { 462 | padding: 0 var(--spacing-large); 463 | } 464 | 465 | 466 | /* 467 | Search box 468 | */ 469 | 470 | #MSearchBox { 471 | height: var(--searchbar-height); 472 | background: var(--searchbar-background); 473 | border-radius: var(--searchbar-height); 474 | border: 1px solid var(--separator-color); 475 | overflow: hidden; 476 | width: var(--searchbar-width); 477 | position: relative; 478 | box-shadow: none; 479 | display: block; 480 | margin-top: 0; 481 | } 482 | 483 | .left #MSearchSelect { 484 | left: 0; 485 | } 486 | 487 | .tabs .left #MSearchSelect { 488 | padding-left: 0; 489 | } 490 | 491 | .tabs #MSearchBox { 492 | position: absolute; 493 | right: var(--spacing-medium); 494 | } 495 | 496 | @media screen and (max-width: 767px) { 497 | .tabs #MSearchBox { 498 | position: relative; 499 | right: 0; 500 | margin-left: var(--spacing-medium); 501 | margin-top: 0; 502 | } 503 | } 504 | 505 | #MSearchSelectWindow, #MSearchResultsWindow { 506 | z-index: 9999; 507 | } 508 | 509 | #MSearchBox.MSearchBoxActive { 510 | border-color: var(--primary-color); 511 | box-shadow: inset 0 0 0 1px var(--primary-color); 512 | } 513 | 514 | #main-menu > li:last-child { 515 | margin-right: 0; 516 | } 517 | 518 | @media screen and (max-width: 767px) { 519 | #main-menu > li:last-child { 520 | height: 50px; 521 | } 522 | } 523 | 524 | #MSearchField { 525 | font-size: var(--navigation-font-size); 526 | height: calc(var(--searchbar-height) - 2px); 527 | background: transparent; 528 | width: calc(var(--searchbar-width) - 64px); 529 | } 530 | 531 | .MSearchBoxActive #MSearchField { 532 | color: var(--searchbar-foreground); 533 | } 534 | 535 | #MSearchSelect { 536 | top: calc(calc(var(--searchbar-height) / 2) - 11px); 537 | } 538 | 539 | .left #MSearchSelect { 540 | padding-left: 8px; 541 | } 542 | 543 | #MSearchBox span.left, #MSearchBox span.right { 544 | background: none; 545 | } 546 | 547 | #MSearchBox span.right { 548 | padding-top: calc(calc(var(--searchbar-height) / 2) - 12px); 549 | position: absolute; 550 | right: var(--spacing-small); 551 | } 552 | 553 | .tabs #MSearchBox span.right { 554 | top: calc(calc(var(--searchbar-height) / 2) - 12px); 555 | } 556 | 557 | @keyframes slideInSearchResults { 558 | from { 559 | opacity: 0; 560 | transform: translate(0, 15px); 561 | } 562 | 563 | to { 564 | opacity: 1; 565 | transform: translate(0, 20px); 566 | } 567 | } 568 | 569 | #MSearchResultsWindow { 570 | left: auto !important; 571 | right: var(--spacing-medium); 572 | border-radius: var(--border-radius-large); 573 | border: 1px solid var(--separator-color); 574 | transform: translate(0, 20px); 575 | box-shadow: var(--box-shadow); 576 | animation: ease-out 280ms slideInSearchResults; 577 | background: var(--page-background-color); 578 | } 579 | 580 | iframe#MSearchResults { 581 | margin: 4px; 582 | } 583 | 584 | iframe { 585 | color-scheme: normal; 586 | } 587 | 588 | @media (prefers-color-scheme: dark) { 589 | html:not(.light-mode) iframe#MSearchResults { 590 | filter: invert() hue-rotate(180deg); 591 | } 592 | } 593 | 594 | html.dark-mode iframe#MSearchResults { 595 | filter: invert() hue-rotate(180deg); 596 | } 597 | 598 | #MSearchSelectWindow { 599 | border: 1px solid var(--separator-color); 600 | border-radius: var(--border-radius-medium); 601 | box-shadow: var(--box-shadow); 602 | background: var(--page-background-color); 603 | } 604 | 605 | #MSearchSelectWindow a.SelectItem { 606 | font-size: var(--navigation-font-size); 607 | line-height: var(--content-line-height); 608 | margin: 0 var(--spacing-small); 609 | border-radius: var(--border-radius-small); 610 | color: var(--page-foreground-color) !important; 611 | font-weight: normal; 612 | } 613 | 614 | #MSearchSelectWindow a.SelectItem:hover { 615 | background: var(--menu-focus-background); 616 | color: var(--menu-focus-foreground) !important; 617 | } 618 | 619 | @media screen and (max-width: 767px) { 620 | #MSearchBox { 621 | margin-top: var(--spacing-medium); 622 | margin-bottom: var(--spacing-medium); 623 | width: calc(100vw - 30px); 624 | } 625 | 626 | #main-menu > li:last-child { 627 | float: none !important; 628 | } 629 | 630 | #MSearchField { 631 | width: calc(100vw - 110px); 632 | } 633 | 634 | @keyframes slideInSearchResultsMobile { 635 | from { 636 | opacity: 0; 637 | transform: translate(0, 15px); 638 | } 639 | 640 | to { 641 | opacity: 1; 642 | transform: translate(0, 20px); 643 | } 644 | } 645 | 646 | #MSearchResultsWindow { 647 | left: var(--spacing-medium) !important; 648 | right: var(--spacing-medium); 649 | overflow: auto; 650 | transform: translate(0, 20px); 651 | animation: ease-out 280ms slideInSearchResultsMobile; 652 | } 653 | 654 | /* 655 | * Overwrites for fixing the searchbox on mobile in doxygen 1.9.2 656 | */ 657 | label.main-menu-btn ~ #searchBoxPos1 { 658 | top: 3px !important; 659 | right: 6px !important; 660 | left: 45px; 661 | display: flex; 662 | } 663 | 664 | label.main-menu-btn ~ #searchBoxPos1 > #MSearchBox { 665 | margin-top: 0; 666 | margin-bottom: 0; 667 | flex-grow: 2; 668 | float: left; 669 | } 670 | } 671 | 672 | /* 673 | Tree view 674 | */ 675 | 676 | #side-nav { 677 | padding: 0 !important; 678 | background: var(--side-nav-background); 679 | } 680 | 681 | @media screen and (max-width: 767px) { 682 | #side-nav { 683 | display: none; 684 | } 685 | 686 | #doc-content { 687 | margin-left: 0 !important; 688 | height: auto !important; 689 | padding-bottom: calc(2 * var(--spacing-large)); 690 | } 691 | } 692 | 693 | #nav-tree { 694 | background: transparent; 695 | } 696 | 697 | #nav-tree .label { 698 | font-size: var(--navigation-font-size); 699 | } 700 | 701 | #nav-tree .item { 702 | height: var(--tree-item-height); 703 | line-height: var(--tree-item-height); 704 | } 705 | 706 | #nav-sync { 707 | top: 12px !important; 708 | right: 12px; 709 | } 710 | 711 | #nav-tree .selected { 712 | text-shadow: none; 713 | background-image: none; 714 | background-color: transparent; 715 | box-shadow: inset 4px 0 0 0 var(--primary-color); 716 | } 717 | 718 | #nav-tree a { 719 | color: var(--side-nav-foreground) !important; 720 | font-weight: normal; 721 | } 722 | 723 | #nav-tree a:focus { 724 | outline-style: auto; 725 | } 726 | 727 | #nav-tree .arrow { 728 | opacity: var(--side-nav-arrow-opacity); 729 | } 730 | 731 | .arrow { 732 | color: inherit; 733 | cursor: pointer; 734 | font-size: 45%; 735 | vertical-align: middle; 736 | margin-right: 2px; 737 | font-family: serif; 738 | height: auto; 739 | text-align: right; 740 | } 741 | 742 | #nav-tree div.item:hover .arrow, #nav-tree a:focus .arrow { 743 | opacity: var(--side-nav-arrow-hover-opacity); 744 | } 745 | 746 | #nav-tree .selected a { 747 | color: var(--primary-color) !important; 748 | font-weight: bolder; 749 | font-weight: 600; 750 | } 751 | 752 | .ui-resizable-e { 753 | background: var(--separator-color); 754 | width: 1px; 755 | } 756 | 757 | /* 758 | Contents 759 | */ 760 | 761 | div.header { 762 | border-bottom: 1px solid var(--separator-color); 763 | background-color: var(--page-background-color); 764 | background-image: none; 765 | } 766 | 767 | div.contents, div.header .title, div.header .summary { 768 | max-width: var(--content-maxwidth); 769 | } 770 | 771 | div.contents, div.header .title { 772 | line-height: initial; 773 | margin: calc(var(--spacing-medium) + .2em) auto var(--spacing-medium) auto; 774 | } 775 | 776 | div.header .summary { 777 | margin: var(--spacing-medium) auto 0 auto; 778 | } 779 | 780 | div.headertitle { 781 | padding: 0; 782 | } 783 | 784 | div.header .title { 785 | font-weight: 600; 786 | font-size: 210%; 787 | padding: var(--spacing-medium) var(--spacing-large); 788 | word-break: break-word; 789 | } 790 | 791 | div.header .summary { 792 | width: auto; 793 | display: block; 794 | float: none; 795 | padding: 0 var(--spacing-large); 796 | } 797 | 798 | td.memSeparator { 799 | border-color: var(--separator-color); 800 | } 801 | 802 | .mdescLeft, .mdescRight, .memItemLeft, .memItemRight, .memTemplItemLeft, .memTemplItemRight, .memTemplParams { 803 | background: var(--code-background); 804 | } 805 | 806 | .mdescRight { 807 | color: var(--page-secondary-foreground-color); 808 | } 809 | 810 | span.mlabel { 811 | background: var(--primary-color); 812 | border: none; 813 | padding: 4px 9px; 814 | border-radius: 12px; 815 | margin-right: var(--spacing-medium); 816 | } 817 | 818 | span.mlabel:last-of-type { 819 | margin-right: 2px; 820 | } 821 | 822 | div.contents { 823 | padding: 0 var(--spacing-large); 824 | } 825 | 826 | div.contents p, div.contents li { 827 | line-height: var(--content-line-height); 828 | } 829 | 830 | div.contents div.dyncontent { 831 | margin: var(--spacing-medium) 0; 832 | } 833 | 834 | @media (prefers-color-scheme: dark) { 835 | html:not(.light-mode) div.contents div.dyncontent img, 836 | html:not(.light-mode) div.contents center img, 837 | html:not(.light-mode) div.contents table img, 838 | html:not(.light-mode) div.contents div.dyncontent iframe, 839 | html:not(.light-mode) div.contents center iframe, 840 | html:not(.light-mode) div.contents table iframe { 841 | filter: hue-rotate(180deg) invert(); 842 | } 843 | } 844 | 845 | html.dark-mode div.contents div.dyncontent img, 846 | html.dark-mode div.contents center img, 847 | html.dark-mode div.contents table img, 848 | html.dark-mode div.contents div.dyncontent iframe, 849 | html.dark-mode div.contents center iframe, 850 | html.dark-mode div.contents table iframe { 851 | filter: hue-rotate(180deg) invert(); 852 | } 853 | 854 | h2.groupheader { 855 | border-bottom: 1px solid var(--separator-color); 856 | color: var(--page-foreground-color); 857 | } 858 | 859 | blockquote { 860 | padding: var(--spacing-small) var(--spacing-medium); 861 | background: var(--blockquote-background); 862 | color: var(--blockquote-foreground); 863 | border-left: 2px solid var(--blockquote-foreground); 864 | margin: 0; 865 | } 866 | 867 | blockquote p { 868 | margin: var(--spacing-small) 0 var(--spacing-medium) 0; 869 | } 870 | .paramname { 871 | font-weight: 600; 872 | color: var(--primary-dark-color); 873 | } 874 | 875 | .glow { 876 | text-shadow: 0 0 15px var(--primary-light-color) !important; 877 | } 878 | 879 | .alphachar a { 880 | color: var(--page-foreground-color); 881 | } 882 | 883 | /* 884 | Table of Contents 885 | */ 886 | 887 | div.toc { 888 | background-color: var(--side-nav-background); 889 | border: 1px solid var(--separator-color); 890 | border-radius: var(--border-radius-medium); 891 | box-shadow: var(--box-shadow); 892 | padding: 0 var(--spacing-large); 893 | margin: 0 0 var(--spacing-medium) var(--spacing-medium); 894 | } 895 | 896 | div.toc h3 { 897 | color: var(--side-nav-foreground); 898 | font-size: var(--navigation-font-size); 899 | margin: var(--spacing-large) 0; 900 | } 901 | 902 | div.toc li { 903 | font-size: var(--navigation-font-size); 904 | padding: 0; 905 | background: none; 906 | } 907 | 908 | div.toc li:before { 909 | content: '↓'; 910 | font-weight: 800; 911 | font-family: var(--font-family); 912 | margin-right: var(--spacing-small); 913 | color: var(--side-nav-foreground); 914 | opacity: .4; 915 | } 916 | 917 | div.toc ul li.level1 { 918 | margin: 0; 919 | } 920 | 921 | div.toc ul li.level2, div.toc ul li.level3 { 922 | margin-top: 0; 923 | } 924 | 925 | 926 | @media screen and (max-width: 767px) { 927 | div.toc { 928 | float: none; 929 | width: auto; 930 | margin: 0 0 var(--spacing-medium) 0; 931 | } 932 | } 933 | 934 | /* 935 | Code & Fragments 936 | */ 937 | 938 | code, div.fragment, pre.fragment { 939 | border-radius: var(--border-radius-small); 940 | border: none; 941 | overflow: hidden; 942 | } 943 | 944 | code { 945 | display: inline; 946 | background: var(--code-background); 947 | color: var(--code-foreground); 948 | padding: 2px 6px; 949 | word-break: break-word; 950 | } 951 | 952 | div.fragment, pre.fragment { 953 | margin: var(--spacing-medium) 0; 954 | padding: 14px 16px; 955 | background: var(--fragment-background); 956 | color: var(--fragment-foreground); 957 | overflow-x: auto; 958 | } 959 | 960 | @media screen and (max-width: 767px) { 961 | div.fragment, pre.fragment { 962 | border-top-right-radius: 0; 963 | border-bottom-right-radius: 0; 964 | } 965 | 966 | .contents > div.fragment, .textblock > div.fragment, .textblock > pre.fragment { 967 | margin: var(--spacing-medium) calc(0px - var(--spacing-large)); 968 | border-radius: 0; 969 | } 970 | 971 | .textblock li > .fragment { 972 | margin: var(--spacing-medium) calc(0px - var(--spacing-large)); 973 | } 974 | 975 | .memdoc li > .fragment { 976 | margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); 977 | } 978 | 979 | .memdoc > div.fragment, .memdoc > pre.fragment, dl dd > div.fragment, dl dd pre.fragment { 980 | margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); 981 | border-radius: 0; 982 | } 983 | } 984 | 985 | code, code a, pre.fragment, div.fragment, div.fragment .line, div.fragment span, div.fragment .line a, div.fragment .line span { 986 | font-family: var(--font-family-monospace); 987 | font-size: var(--code-font-size) !important; 988 | } 989 | 990 | div.line:after { 991 | margin-right: var(--spacing-medium); 992 | } 993 | 994 | div.fragment .line, pre.fragment { 995 | white-space: pre; 996 | word-wrap: initial; 997 | line-height: var(--fragment-lineheight); 998 | } 999 | 1000 | div.fragment span.keyword { 1001 | color: var(--fragment-keyword); 1002 | } 1003 | 1004 | div.fragment span.keywordtype { 1005 | color: var(--fragment-keywordtype); 1006 | } 1007 | 1008 | div.fragment span.keywordflow { 1009 | color: var(--fragment-keywordflow); 1010 | } 1011 | 1012 | div.fragment span.stringliteral { 1013 | color: var(--fragment-token) 1014 | } 1015 | 1016 | div.fragment span.comment { 1017 | color: var(--fragment-comment); 1018 | } 1019 | 1020 | div.fragment a.code { 1021 | color: var(--fragment-link) !important; 1022 | } 1023 | 1024 | div.fragment span.preprocessor { 1025 | color: var(--fragment-preprocessor); 1026 | } 1027 | 1028 | div.fragment span.lineno { 1029 | display: inline-block; 1030 | width: 27px; 1031 | border-right: none; 1032 | background: var(--fragment-linenumber-background); 1033 | color: var(--fragment-linenumber-color); 1034 | } 1035 | 1036 | div.fragment span.lineno a { 1037 | background: none; 1038 | color: var(--fragment-link) !important; 1039 | } 1040 | 1041 | div.fragment .line:first-child .lineno { 1042 | box-shadow: -999999px 0px 0 999999px var(--fragment-linenumber-background), -999998px 0px 0 999999px var(--fragment-linenumber-border); 1043 | } 1044 | 1045 | /* 1046 | dl warning, attention, note, deprecated, bug, ... 1047 | */ 1048 | 1049 | dl.warning, dl.attention, dl.note, dl.deprecated, dl.bug, dl.invariant, dl.pre { 1050 | padding: var(--spacing-medium); 1051 | margin: var(--spacing-medium) 0; 1052 | color: var(--page-background-color); 1053 | overflow: hidden; 1054 | margin-left: 0; 1055 | border-radius: var(--border-radius-small); 1056 | } 1057 | 1058 | dl.section dd { 1059 | margin-bottom: 2px; 1060 | } 1061 | 1062 | dl.warning, dl.attention { 1063 | background: var(--warning-color); 1064 | border-left: 8px solid var(--warning-color-dark); 1065 | color: var(--warning-color-darker); 1066 | } 1067 | 1068 | dl.warning dt, dl.attention dt { 1069 | color: var(--warning-color-dark); 1070 | } 1071 | 1072 | dl.note { 1073 | background: var(--note-color); 1074 | border-left: 8px solid var(--note-color-dark); 1075 | color: var(--note-color-darker); 1076 | } 1077 | 1078 | dl.note dt { 1079 | color: var(--note-color-dark); 1080 | } 1081 | 1082 | dl.bug { 1083 | background: var(--bug-color); 1084 | border-left: 8px solid var(--bug-color-dark); 1085 | color: var(--bug-color-darker); 1086 | } 1087 | 1088 | dl.bug dt a { 1089 | color: var(--bug-color-dark) !important; 1090 | } 1091 | 1092 | dl.deprecated { 1093 | background: var(--deprecated-color); 1094 | border-left: 8px solid var(--deprecated-color-dark); 1095 | color: var(--deprecated-color-darker); 1096 | } 1097 | 1098 | dl.deprecated dt a { 1099 | color: var(--deprecated-color-dark) !important; 1100 | } 1101 | 1102 | dl.section dd, dl.bug dd, dl.deprecated dd { 1103 | margin-inline-start: 0px; 1104 | } 1105 | 1106 | dl.invariant, dl.pre { 1107 | background: var(--invariant-color); 1108 | border-left: 8px solid var(--invariant-color-dark); 1109 | color: var(--invariant-color-darker); 1110 | } 1111 | 1112 | /* 1113 | memitem 1114 | */ 1115 | 1116 | div.memdoc, div.memproto, h2.memtitle { 1117 | box-shadow: none; 1118 | background-image: none; 1119 | border: none; 1120 | } 1121 | 1122 | div.memdoc { 1123 | padding: 0 var(--spacing-medium); 1124 | background: var(--page-background-color); 1125 | } 1126 | 1127 | h2.memtitle, div.memitem { 1128 | border: 1px solid var(--separator-color); 1129 | } 1130 | 1131 | div.memproto, h2.memtitle { 1132 | background: var(--code-background); 1133 | text-shadow: none; 1134 | } 1135 | 1136 | h2.memtitle { 1137 | font-weight: 500; 1138 | font-family: monospace, fixed; 1139 | border-bottom: none; 1140 | border-top-left-radius: var(--border-radius-medium); 1141 | border-top-right-radius: var(--border-radius-medium); 1142 | word-break: break-all; 1143 | } 1144 | 1145 | a:target + h2.memtitle, a:target + h2.memtitle + div.memitem { 1146 | border-color: var(--primary-light-color); 1147 | } 1148 | 1149 | a:target + h2.memtitle { 1150 | box-shadow: -3px -3px 3px 0 var(--primary-lightest-color), 3px -3px 3px 0 var(--primary-lightest-color); 1151 | } 1152 | 1153 | a:target + h2.memtitle + div.memitem { 1154 | box-shadow: 0 0 10px 0 var(--primary-lighter-color); 1155 | } 1156 | 1157 | div.memitem { 1158 | border-top-right-radius: var(--border-radius-medium); 1159 | border-bottom-right-radius: var(--border-radius-medium); 1160 | border-bottom-left-radius: var(--border-radius-medium); 1161 | overflow: hidden; 1162 | display: block !important; 1163 | } 1164 | 1165 | div.memdoc { 1166 | border-radius: 0; 1167 | } 1168 | 1169 | div.memproto { 1170 | border-radius: 0 var(--border-radius-small) 0 0; 1171 | overflow: auto; 1172 | border-bottom: 1px solid var(--separator-color); 1173 | padding: var(--spacing-medium); 1174 | margin-bottom: -1px; 1175 | } 1176 | 1177 | div.memtitle { 1178 | border-top-right-radius: var(--border-radius-medium); 1179 | border-top-left-radius: var(--border-radius-medium); 1180 | } 1181 | 1182 | div.memproto table.memname { 1183 | font-family: monospace, fixed; 1184 | color: var(--page-foreground-color); 1185 | } 1186 | 1187 | table.mlabels, table.mlabels > tbody { 1188 | display: block; 1189 | } 1190 | 1191 | td.mlabels-left { 1192 | width: auto; 1193 | } 1194 | 1195 | table.mlabels > tbody > tr:first-child { 1196 | display: flex; 1197 | justify-content: space-between; 1198 | flex-wrap: wrap; 1199 | } 1200 | 1201 | .memname, .memitem span.mlabels { 1202 | margin: 0 1203 | } 1204 | 1205 | /* 1206 | reflist 1207 | */ 1208 | 1209 | dl.reflist { 1210 | box-shadow: var(--box-shadow); 1211 | border-radius: var(--border-radius-medium); 1212 | border: 1px solid var(--separator-color); 1213 | overflow: hidden; 1214 | padding: 0; 1215 | } 1216 | 1217 | 1218 | dl.reflist dt, dl.reflist dd { 1219 | box-shadow: none; 1220 | text-shadow: none; 1221 | background-image: none; 1222 | border: none; 1223 | padding: 12px; 1224 | } 1225 | 1226 | 1227 | dl.reflist dt { 1228 | font-weight: 500; 1229 | border-radius: 0; 1230 | background: var(--code-background); 1231 | border-bottom: 1px solid var(--separator-color); 1232 | color: var(--page-foreground-color) 1233 | } 1234 | 1235 | 1236 | dl.reflist dd { 1237 | background: none; 1238 | } 1239 | 1240 | /* 1241 | Table 1242 | */ 1243 | 1244 | table.markdownTable, table.fieldtable { 1245 | width: 100%; 1246 | border: 1px solid var(--separator-color); 1247 | margin: var(--spacing-medium) 0; 1248 | } 1249 | 1250 | table.fieldtable { 1251 | box-shadow: none; 1252 | border-radius: var(--border-radius-small); 1253 | } 1254 | 1255 | th.markdownTableHeadLeft, th.markdownTableHeadRight, th.markdownTableHeadCenter, th.markdownTableHeadNone { 1256 | background: var(--tablehead-background); 1257 | color: var(--tablehead-foreground); 1258 | font-weight: 600; 1259 | font-size: var(--page-font-size); 1260 | } 1261 | 1262 | table.markdownTable td, table.markdownTable th, table.fieldtable dt { 1263 | border: 1px solid var(--separator-color); 1264 | padding: var(--spacing-small) var(--spacing-medium); 1265 | } 1266 | 1267 | table.fieldtable th { 1268 | font-size: var(--page-font-size); 1269 | font-weight: 600; 1270 | background-image: none; 1271 | background-color: var(--tablehead-background); 1272 | color: var(--tablehead-foreground); 1273 | border-bottom: 1px solid var(--separator-color); 1274 | } 1275 | 1276 | .fieldtable td.fieldtype, .fieldtable td.fieldname { 1277 | border-bottom: 1px solid var(--separator-color); 1278 | border-right: 1px solid var(--separator-color); 1279 | } 1280 | 1281 | .fieldtable td.fielddoc { 1282 | border-bottom: 1px solid var(--separator-color); 1283 | } 1284 | 1285 | .memberdecls td.glow, .fieldtable tr.glow { 1286 | background-color: var(--primary-light-color); 1287 | box-shadow: 0 0 15px var(--primary-lighter-color); 1288 | } 1289 | 1290 | table.memberdecls { 1291 | display: block; 1292 | overflow-x: auto; 1293 | overflow-y: hidden; 1294 | } 1295 | 1296 | 1297 | /* 1298 | Horizontal Rule 1299 | */ 1300 | 1301 | hr { 1302 | margin-top: var(--spacing-large); 1303 | margin-bottom: var(--spacing-large); 1304 | border-top:1px solid var(--separator-color); 1305 | } 1306 | 1307 | .contents hr { 1308 | box-shadow: var(--content-maxwidth) 0 0 0 var(--separator-color), calc(0px - var(--content-maxwidth)) 0 0 0 var(--separator-color); 1309 | } 1310 | 1311 | .contents img, .contents .center, .contents center { 1312 | max-width: 100%; 1313 | overflow: scroll; 1314 | } 1315 | 1316 | /* 1317 | Directories 1318 | */ 1319 | div.directory { 1320 | border-top: 1px solid var(--separator-color); 1321 | border-bottom: 1px solid var(--separator-color); 1322 | width: auto; 1323 | } 1324 | 1325 | table.directory { 1326 | font-family: var(--font-family); 1327 | font-size: var(--page-font-size); 1328 | font-weight: normal; 1329 | } 1330 | 1331 | .directory td.entry { 1332 | padding: var(--spacing-small); 1333 | display: flex; 1334 | align-items: center; 1335 | } 1336 | 1337 | .directory tr.even { 1338 | background-color: var(--odd-color); 1339 | } 1340 | 1341 | .icona { 1342 | width: auto; 1343 | height: auto; 1344 | margin: 0 var(--spacing-small); 1345 | } 1346 | 1347 | .icon { 1348 | background: var(--primary-color); 1349 | width: 18px; 1350 | height: 18px; 1351 | line-height: 18px; 1352 | } 1353 | 1354 | .iconfopen, .icondoc, .iconfclosed { 1355 | background-position: center; 1356 | margin-bottom: 0; 1357 | } 1358 | 1359 | .icondoc { 1360 | filter: saturate(0.2); 1361 | } 1362 | 1363 | @media screen and (max-width: 767px) { 1364 | div.directory { 1365 | margin-left: calc(0px - var(--spacing-medium)); 1366 | margin-right: calc(0px - var(--spacing-medium)); 1367 | } 1368 | } 1369 | 1370 | @media (prefers-color-scheme: dark) { 1371 | html:not(.light-mode) .iconfopen, html:not(.light-mode) .iconfclosed { 1372 | filter: hue-rotate(180deg) invert(); 1373 | } 1374 | } 1375 | 1376 | html.dark-mode .iconfopen, html.dark-mode .iconfclosed { 1377 | filter: hue-rotate(180deg) invert(); 1378 | } 1379 | 1380 | /* 1381 | Class list 1382 | */ 1383 | 1384 | .classindex dl.odd { 1385 | background: var(--odd-color); 1386 | border-radius: var(--border-radius-small); 1387 | } 1388 | 1389 | @media screen and (max-width: 767px) { 1390 | .classindex { 1391 | margin: 0 calc(0px - var(--spacing-small)); 1392 | } 1393 | } 1394 | 1395 | /* 1396 | Footer and nav-path 1397 | */ 1398 | 1399 | #nav-path { 1400 | margin-bottom: -1px; 1401 | width: 100%; 1402 | } 1403 | 1404 | #nav-path ul { 1405 | background-image: none; 1406 | background: var(--page-background-color); 1407 | border: none; 1408 | border-top: 1px solid var(--separator-color); 1409 | border-bottom: 1px solid var(--separator-color); 1410 | font-size: var(--navigation-font-size); 1411 | } 1412 | 1413 | img.footer { 1414 | width: 60px; 1415 | } 1416 | 1417 | .navpath li.footer { 1418 | color: var(--page-secondary-foreground-color); 1419 | } 1420 | 1421 | address.footer { 1422 | margin-bottom: var(--spacing-large); 1423 | } 1424 | 1425 | #nav-path li.navelem { 1426 | background-image: none; 1427 | display: flex; 1428 | align-items: center; 1429 | } 1430 | 1431 | .navpath li.navelem a { 1432 | text-shadow: none; 1433 | display: inline-block; 1434 | color: var(--primary-color) !important; 1435 | } 1436 | 1437 | .navpath li.navelem b { 1438 | color: var(--primary-dark-color); 1439 | font-weight: 500; 1440 | } 1441 | 1442 | li.navelem { 1443 | padding: 0; 1444 | margin-left: -8px; 1445 | } 1446 | 1447 | li.navelem:first-child { 1448 | margin-left: var(--spacing-large); 1449 | } 1450 | 1451 | li.navelem:first-child:before { 1452 | display: none; 1453 | } 1454 | 1455 | #nav-path li.navelem:after { 1456 | content: ''; 1457 | border: 5px solid var(--page-background-color); 1458 | border-bottom-color: transparent; 1459 | border-right-color: transparent; 1460 | border-top-color: transparent; 1461 | transform: scaleY(4.2); 1462 | z-index: 10; 1463 | margin-left: 6px; 1464 | } 1465 | 1466 | #nav-path li.navelem:before { 1467 | content: ''; 1468 | border: 5px solid var(--separator-color); 1469 | border-bottom-color: transparent; 1470 | border-right-color: transparent; 1471 | border-top-color: transparent; 1472 | transform: scaleY(3.2); 1473 | margin-right: var(--spacing-small); 1474 | } 1475 | 1476 | .navpath li.navelem a:hover { 1477 | color: var(--primary-color); 1478 | } 1479 | 1480 | /* 1481 | Optional Dark mode toggle button 1482 | */ 1483 | 1484 | doxygen-awesome-dark-mode-toggle { 1485 | display: inline-block; 1486 | margin: 0 0 0 var(--spacing-small); 1487 | padding: 0; 1488 | width: var(--searchbar-height); 1489 | height: var(--searchbar-height); 1490 | background: none; 1491 | border: none; 1492 | font-size: 23px; 1493 | border-radius: var(--border-radius-medium); 1494 | vertical-align: middle; 1495 | text-align: center; 1496 | line-height: var(--searchbar-height); 1497 | } 1498 | 1499 | doxygen-awesome-dark-mode-toggle:hover { 1500 | background: var(--separator-color); 1501 | } 1502 | 1503 | doxygen-awesome-dark-mode-toggle:after { 1504 | content: var(--darkmode-toggle-button-icon) 1505 | } 1506 | -------------------------------------------------------------------------------- /docs/extra/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 15 | 16 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/extra/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $projectname: $title 10 | $title 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | $treeview 21 | $search 22 | $mathjax 23 | 24 | $extrastylesheet 25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | 34 |
35 | 36 | 37 |
38 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 52 | 53 | 54 | 55 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
48 |
$projectname $projectnumber 49 |
50 |
$projectbrief
51 |
56 |
$projectbrief
57 |
$searchbox
$searchbox
75 |
76 | 77 | 78 | -------------------------------------------------------------------------------- /docs/images/QB_Logo_Small-55x55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qu-Bit-Electronix/Aurora-SDK/8b0378bd8f54d770a329a57250dcedb1f68fcbaa/docs/images/QB_Logo_Small-55x55.png -------------------------------------------------------------------------------- /docs/md/a1_Building the Examples.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Qu-Bit-Electronix/Aurora-SDK/8b0378bd8f54d770a329a57250dcedb1f68fcbaa/docs/md/a1_Building the Examples.md -------------------------------------------------------------------------------- /include/aurora.h: -------------------------------------------------------------------------------- 1 | /** Aurora Hardware Support File */ 2 | #pragma once 3 | #ifndef AURORA_HW_H 4 | #define AURORA_HW_H 5 | 6 | #include "daisy_seed.h" 7 | #include "fatfs.h" 8 | 9 | #define DTCMRAM __attribute__((section(".dtcmram_bss"))) 10 | #define D2RAM __attribute__((section(".d2_bss"))) 11 | #define D2RAM2 __attribute__((section(".d2r2_bss"))) 12 | #define ITCMRAM __attribute__((section(".itcmram_bss"))) 13 | 14 | namespace aurora 15 | { 16 | 17 | /** @brief const used internally within Aurora Firmware to manage buffer memory 18 | * This can be safely exceeded in custom firmware. 19 | */ 20 | static constexpr int kMaxBlockSize = 96; 21 | 22 | /** @brief Global buffers for the LED driver 23 | * Non-cached for DMA usage. 24 | */ 25 | static daisy::LedDriverPca9685<2, true>::DmaBuffer DMA_BUFFER_MEM_SECTION 26 | led_dma_buffer_a, 27 | led_dma_buffer_b; 28 | 29 | /** @brief global USB host handle accessor 30 | * This is left global so that it is guaranteed to be within the AXI SRAM 31 | * with the aurora_sram.lds linker file. 32 | */ 33 | daisy::USBHostHandle usb; 34 | 35 | /** @brief global accessor to the FatFS interface. 36 | * This is left global so that it is guaranteed to be within the AXI SRAM 37 | * with the aurora_sram.lds linker file. 38 | */ 39 | daisy::FatFSInterface fatfs_interface; 40 | 41 | /** @brief indexed accessors for knob controls 42 | * Example usage: 43 | * float val = hw.GetKnobValue(KNOB_TIME); 44 | */ 45 | enum ControlKnobs 46 | { 47 | KNOB_TIME, 48 | KNOB_REFLECT, 49 | KNOB_MIX, 50 | KNOB_ATMOSPHERE, 51 | KNOB_BLUR, 52 | KNOB_WARP, 53 | KNOB_LAST, 54 | }; 55 | 56 | /** @brief indexed accessors for CV controls 57 | * Example usage: 58 | * float val = hw.GetCvValue(CV_ATMOSPHERE); 59 | */ 60 | enum ControlCVs 61 | { 62 | CV_ATMOSPHERE, 63 | CV_TIME, 64 | CV_MIX, 65 | CV_REFLECT, 66 | CV_BLUR, 67 | CV_WARP, 68 | CV_LAST, 69 | }; 70 | 71 | /** @brief indexed accessors for momentary switches 72 | * Example usage: 73 | * bool state = hw.GetSwitch(SW_FREEZE).Pressed(); 74 | */ 75 | enum Switches 76 | { 77 | SW_FREEZE, 78 | SW_REVERSE, 79 | SW_SHIFT, 80 | SW_LAST, 81 | }; 82 | 83 | /** @brief indexed accessors for momentary switches 84 | * Example usage: 85 | * bool trig = hw.GetGateTrig(GATE_FREEZE); 86 | */ 87 | enum Gates 88 | { 89 | GATE_FREEZE, 90 | GATE_REVERSE, 91 | GATE_LAST, 92 | }; 93 | 94 | 95 | /** @brief indexed accessors for RGB LEDs 96 | * Example usage: 97 | * hw.SetLed(LED_FREEZE, 0.f, 0.f, 1.f); 98 | */ 99 | enum Leds 100 | { 101 | LED_REVERSE, 102 | LED_FREEZE, 103 | LED_1, 104 | LED_2, 105 | LED_3, 106 | LED_4, 107 | LED_5, 108 | LED_6, 109 | LED_BOT_1, 110 | LED_BOT_2, 111 | LED_BOT_3, 112 | LED_LAST, 113 | }; 114 | 115 | /** @brief Calibration data container for Aurora 116 | * This data is calibrated from the Qu-Bit default Aurora firmware. 117 | * It is advised not to save over this data unless you are prepared to recalibrate. 118 | */ 119 | struct CalibrationData 120 | { 121 | CalibrationData() : warp_scale(60.f), warp_offset(0.f), cv_offset{0.f} {} 122 | float warp_scale, warp_offset; 123 | float cv_offset[CV_LAST]; 124 | 125 | /** @brief checks sameness */ 126 | bool operator==(const CalibrationData &rhs) 127 | { 128 | if(warp_scale != rhs.warp_scale) 129 | { 130 | return false; 131 | } 132 | else if(warp_offset != rhs.warp_offset) 133 | { 134 | return false; 135 | } 136 | else 137 | { 138 | for(int i = 0; i < CV_LAST; i++) 139 | { 140 | if(cv_offset[i] != rhs.cv_offset[i]) 141 | return false; 142 | } 143 | } 144 | return true; 145 | } 146 | 147 | /** @brief Not equal operator */ 148 | bool operator!=(const CalibrationData &rhs) { return !operator==(rhs); } 149 | }; 150 | 151 | /** @brief Hardware support class for the Qu-Bit Aurora 152 | * This should be created, and intitialized at the beginning of any program 153 | * before running anything else. 154 | * 155 | * If you've used the Daisy Seed, etc. before this object takes the place 156 | * of the core "DaisySeed" or other board support objects. 157 | */ 158 | class Hardware 159 | { 160 | public: 161 | /** @brief Empty Constructor 162 | * Call `Init` from main to initialize 163 | */ 164 | Hardware() {} 165 | 166 | /** @brief Empty Destructor 167 | * This object should span the life of the program 168 | */ 169 | ~Hardware() {} 170 | 171 | /** @brief Initialize the hardware. 172 | * Call this function at the start of main() 173 | * 174 | * @param boost true sets the processor to run at the maximum 480MHz, 175 | * false sets the processor to run at 400MHz. 176 | * Defaults to true (480MHz). 177 | */ 178 | void Init(bool boost = true) 179 | { 180 | seed.Init(boost); 181 | hw_version = GetBoardRevision(); 182 | ConfigureAudio(); 183 | ConfigureControls(); 184 | ConfigureLeds(); 185 | seed.adc.Start(); 186 | SetAudioSampleRate(daisy::SaiHandle::Config::SampleRate::SAI_48KHZ); 187 | SetAudioBlockSize(96); 188 | UpdateHidRates(); 189 | 190 | cal_save_flag_ = false; 191 | for(int i = 0; i < CV_LAST; i++) 192 | { 193 | cv_offsets_[i] = 0.f; 194 | } 195 | LoadCalibrationData(); 196 | } 197 | 198 | /** @brief Starts a specified audio callback 199 | * Data is non-interleaved 200 | * (i.e. {{L0, L1, ... , LN},{R0, R1, ... , RN}}) 201 | */ 202 | void StartAudio(daisy::AudioHandle::AudioCallback cb) 203 | { 204 | current_cb_ = cb; 205 | seed.StartAudio(cb); 206 | } 207 | 208 | /** @brief Changes current callback to a new non-interleaved callback */ 209 | void ChangeAudioCallback(daisy::AudioHandle::AudioCallback cb) 210 | { 211 | current_cb_ = cb; 212 | seed.ChangeAudioCallback(cb); 213 | } 214 | 215 | /** @brief Starts a specified Interleaving audio callback */ 216 | void StartAudio(daisy::AudioHandle::InterleavingAudioCallback cb) 217 | { 218 | seed.StartAudio(cb); 219 | } 220 | 221 | /** @brief Changes current callback to a new interleaved callback */ 222 | void ChangeAudioCallback(daisy::AudioHandle::InterleavingAudioCallback cb) 223 | { 224 | seed.ChangeAudioCallback(cb); 225 | } 226 | 227 | /** This starts up a callback that is on the lowest priority interrupt level 228 | * This provides an area for non-background tasks that should interrupt low 229 | * level activity like diskio. 230 | * 231 | * @param cb callback to take place at target frequency 232 | * @param target_freq freq in hz that the callback should take place. 233 | * @param data any data to send through callback; this defaults to nullptr 234 | */ 235 | void StartLowPriorityCallback(daisy::TimerHandle::PeriodElapsedCallback cb, 236 | uint32_t target_freq, 237 | void *data = nullptr) 238 | { 239 | daisy::TimerHandle::Config timcfg; 240 | timcfg.periph = daisy::TimerHandle::Config::Peripheral::TIM_5; 241 | timcfg.dir = daisy::TimerHandle::Config::CounterDir::UP; 242 | auto tim_base_freq = daisy::System::GetPClk2Freq(); 243 | auto tim_target_freq = target_freq; 244 | auto tim_period = tim_base_freq / tim_target_freq; 245 | timcfg.period = tim_period; 246 | timcfg.enable_irq = true; 247 | tim5_handle.Init(timcfg); 248 | tim5_handle.SetCallback(cb, data); 249 | /** Start Audio */ 250 | tim5_handle.Start(); 251 | } 252 | 253 | /** @brief Updates the samplerate to one of the allowed target samplerates. 254 | * This function stops the audio completely, and will cause clicks. 255 | * 256 | * @param sr target samplerate in Hz. Allowed values are 16000, 32000, 48000, 96000 257 | * any other value will fallback to 48kHz 258 | */ 259 | void ChangeSampleRate(int sr) 260 | { 261 | daisy::SaiHandle::Config::SampleRate srval; 262 | size_t new_blocksize = 96; 263 | switch(sr) 264 | { 265 | case 16000: 266 | srval = daisy::SaiHandle::Config::SampleRate::SAI_16KHZ; 267 | break; 268 | case 32000: 269 | srval = daisy::SaiHandle::Config::SampleRate::SAI_32KHZ; 270 | break; 271 | case 48000: 272 | srval = daisy::SaiHandle::Config::SampleRate::SAI_48KHZ; 273 | break; 274 | case 96000: 275 | srval = daisy::SaiHandle::Config::SampleRate::SAI_96KHZ; 276 | new_blocksize = 48; 277 | break; 278 | default: 279 | srval = daisy::SaiHandle::Config::SampleRate::SAI_48KHZ; 280 | break; 281 | } 282 | seed.StopAudio(); 283 | seed.SetAudioSampleRate(srval); 284 | seed.SetAudioBlockSize(new_blocksize); 285 | seed.StartAudio(current_cb_); 286 | } 287 | 288 | /** @brief Stops Audio */ 289 | void StopAudio() { seed.StopAudio(); } 290 | 291 | /** @brief sets the audio sample rate. Audio must be stopped for this to work properly 292 | * @param samplerate target samplerate in in daisy::SaiHandle::Config::SampleRate 293 | */ 294 | void SetAudioSampleRate(daisy::SaiHandle::Config::SampleRate samplerate) 295 | { 296 | seed.SetAudioSampleRate(samplerate); 297 | } 298 | 299 | /** @brief sets the audio sample rate. Audio must be stopped for this to work properly 300 | * @param samplerate target samplerate in Hz. Allowed values are 16000, 32000, 48000, 96000 301 | * any other value will fallback to 48kHz 302 | */ 303 | void SetAudioSampleRate(int samplerate) 304 | { 305 | daisy::SaiHandle::Config::SampleRate srval; 306 | switch(samplerate) 307 | { 308 | case 16000: 309 | srval = daisy::SaiHandle::Config::SampleRate::SAI_16KHZ; 310 | break; 311 | case 32000: 312 | srval = daisy::SaiHandle::Config::SampleRate::SAI_32KHZ; 313 | break; 314 | case 48000: 315 | srval = daisy::SaiHandle::Config::SampleRate::SAI_48KHZ; 316 | break; 317 | case 96000: 318 | srval = daisy::SaiHandle::Config::SampleRate::SAI_96KHZ; 319 | break; 320 | default: 321 | srval = daisy::SaiHandle::Config::SampleRate::SAI_48KHZ; 322 | break; 323 | } 324 | seed.SetAudioSampleRate(srval); 325 | } 326 | 327 | /** @brief returns the sample rate in Hz of the audio engine */ 328 | float AudioSampleRate() { return seed.audio_handle.GetSampleRate(); } 329 | 330 | /** @brief sets the number of samples to process in each audio callback */ 331 | void SetAudioBlockSize(size_t blocksize) 332 | { 333 | seed.SetAudioBlockSize(blocksize); 334 | } 335 | 336 | /** @brief returns the number of samples to process in each audio callback */ 337 | size_t AudioBlockSize() { return seed.AudioBlockSize(); } 338 | 339 | /** @brief returns the rate in Hz that the audio callback gets called */ 340 | float AudioCallbackRate() const { return seed.AudioCallbackRate(); } 341 | 342 | /** @brief sets the state of the LED on the daisy itself */ 343 | void SetTestLed(bool state) { seed.SetLed(state); } 344 | 345 | /** @brief sets all RGB LEDs to off state 346 | * This can be called from the top of wherever LEDs are periodically set 347 | * or within the UI framework's Canvas descriptor via the 348 | * clearFunction_ function. 349 | */ 350 | void ClearLeds() 351 | { 352 | for(int i = 0; i < LED_LAST; i++) 353 | { 354 | SetLed((Leds)i, 0.0f, 0.0f, 0.0f); 355 | } 356 | } 357 | 358 | /** @brief Writes the state of all LEDs to the hardware 359 | * This can be called from a fixed interval in the main loop, 360 | * or within the UI framework's Canvas descriptor via the 361 | * flushFunction_ function. 362 | */ 363 | void WriteLeds() { led_driver_.SwapBuffersAndTransmit(); } 364 | 365 | /** @brief Sets the RGB value of a given LED 366 | * @param idx LED index (one of Leds enum above) 367 | * @param r 0- 1 red value 368 | * @param g 0- 1 green value 369 | * @param b 0- 1 blue value 370 | */ 371 | void SetLed(Leds idx, float r, float g, float b) 372 | { 373 | LedIdx led = LedMap[idx]; 374 | if(led.r != -1) 375 | led_driver_.SetLed(led.r, r); 376 | led_driver_.SetLed(led.g, g); 377 | led_driver_.SetLed(led.b, b); 378 | } 379 | 380 | /** @brief Sets the Color value of an LED 381 | * @param idx LED index (on of Leds enum above) 382 | * @param c Color object containing desired RGB values 383 | */ 384 | void SetLed(Leds idx, daisy::Color c) 385 | { 386 | SetLed(idx, c.Red(), c.Green(), c.Blue()); 387 | } 388 | 389 | /** @brief filters and debounces all control 390 | * This should be run once per audio callback. 391 | */ 392 | void ProcessAllControls() 393 | { 394 | ProcessAnalogControls(); 395 | ProcessDigitalControls(); 396 | } 397 | 398 | /** @brief filters and debounces digital controls (switches) 399 | * This is called from ProcessAllControls, and should be run once per audio callback 400 | */ 401 | void ProcessDigitalControls() 402 | { 403 | for(int i = 0; i < SW_LAST; i++) 404 | { 405 | switches[i].Debounce(); 406 | } 407 | } 408 | 409 | /** @brief filters all analog controls (knobs and CVs) 410 | * This is called from ProcessAllControls, and should be run once per audio callback 411 | */ 412 | void ProcessAnalogControls() 413 | { 414 | for(int i = 0; i < KNOB_LAST; i++) 415 | { 416 | controls[i].Process(); 417 | } 418 | 419 | for(int i = 0; i < CV_LAST; i++) 420 | { 421 | cv[i].Process(); 422 | } 423 | } 424 | /** @brief returns a 0-1 value for the given knob control 425 | * @param ctrl knob index to read from. Should be one of ControlKnob (i.e. KNOB_TIME) 426 | */ 427 | inline float GetKnobValue(int ctrl) const { return controls[ctrl].Value(); } 428 | 429 | /** @brief returns a reference to a given momentary switch 430 | * Example Usage: 431 | * bool state = hw.GetButton(SW_FREEZE).Pressed(); 432 | * @param idx one of the Switches enum values 433 | */ 434 | inline const daisy::Switch &GetButton(int idx) const 435 | { 436 | return switches[idx]; 437 | } 438 | 439 | /** @brief returns true if the gate input just went high 440 | * This is expected to be checked only once per audio callback 441 | * 442 | * @param idx one of the Gates enum values 443 | */ 444 | inline bool GetGateTrig(int idx) { return gates[idx].Trig(); } 445 | 446 | /** @brief returns true if the gate input is currently high 447 | * @param idx one of the Gates enum values 448 | */ 449 | inline bool GetGateState(int idx) { return gates[idx].State(); } 450 | 451 | /** @brief Update HidRates for new Callback rate when samplerate/blocksize change */ 452 | void UpdateHidRates() 453 | { 454 | for(int i = 0; i < CV_LAST; i++) 455 | { 456 | cv[i].SetSampleRate(AudioCallbackRate()); 457 | } 458 | for(int i = 0; i < KNOB_LAST; i++) 459 | { 460 | controls[i].SetSampleRate(AudioCallbackRate()); 461 | } 462 | for(int i = 0; i < SW_LAST; i++) 463 | { 464 | switches[i].SetUpdateRate(AudioCallbackRate()); 465 | } 466 | } 467 | 468 | /** @brief starts the mounting process for USB Drive use if it is present 469 | * 470 | * To access files from a USB drive: the connect, class_active, and disconnect 471 | * callbacks can be used to update an external state machine that can be used 472 | * to trigger interactions with the filesystem. 473 | * 474 | * This process should be done within the main() while loop, and `usb.Process()` 475 | * should be called once per loop. 476 | */ 477 | void PrepareMedia( 478 | daisy::USBHostHandle::ConnectCallback connect_cb = nullptr, 479 | daisy::USBHostHandle::DisconnectCallback disconnect_cb = nullptr, 480 | daisy::USBHostHandle::ClassActiveCallback class_active_cb = nullptr, 481 | daisy::USBHostHandle::ErrorCallback error_cb = nullptr, 482 | void *userdata = nullptr) 483 | { 484 | /** Initialize hardware and set user callbacks */ 485 | daisy::USBHostHandle::Config usbcfg; 486 | usbcfg.connect_callback = connect_cb; 487 | usbcfg.disconnect_callback = disconnect_cb; 488 | usbcfg.class_active_callback = class_active_cb; 489 | usbcfg.error_callback = error_cb; 490 | usbcfg.userdata = userdata; 491 | usb.Init(usbcfg); 492 | 493 | /** Prepare FatFS -- fmount will defer until first attempt to read/write */ 494 | daisy::FatFSInterface::Config fsi_cfg; 495 | fsi_cfg.media = daisy::FatFSInterface::Config::MEDIA_USB; 496 | fatfs_interface.Init(fsi_cfg); 497 | f_mount(&fatfs_interface.GetUSBFileSystem(), 498 | fatfs_interface.GetUSBPath(), 499 | 0); 500 | } 501 | 502 | /** @brief Return a MIDI note number value from -60 to 60 corresponding to 503 | * the -5V to 5V input range of the Warp CV input. 504 | */ 505 | inline float GetWarpVoct() 506 | { 507 | return voct_cal.ProcessInput(cv[CV_WARP].Value()); 508 | } 509 | 510 | /** @brief gets calibrated offset-adjusted CV Value from the hardware. 511 | * @param cv_idx index of the CV to read from. 512 | * @return offset adjusted output (except for warp which is v/oct calibrated) 513 | * @note when returning warp CV from this function it will be with no calibrated 514 | * offset, and is identical to reading from the AnalogControl itself. 515 | */ 516 | inline float GetCvValue(int cv_idx) 517 | { 518 | if(cv_idx != CV_WARP) 519 | return cv[cv_idx].Value() - cv_offsets_[cv_idx]; 520 | else 521 | return cv[cv_idx].Value(); 522 | } 523 | 524 | /** @brief delay function; same as System::Delay(), and should not be called 525 | * from any interrupt callbacks (LowPriorityCallback/AudioCallback). 526 | * 527 | * @param del number of milliseconds to delay 528 | */ 529 | void DelayMs(size_t del) { seed.DelayMs(del); } 530 | 531 | /** @brief called during a customized calibration UI to record the 1V value */ 532 | inline void CalibrateV1(float v1) { warp_v1_ = v1; } 533 | 534 | /** @brief called during a customized calibration UI to record the 3V value 535 | * and set that calibraiton has completed and can be saved. 536 | */ 537 | inline void CalibrateV3(float v3) 538 | { 539 | warp_v3_ = v3; 540 | voct_cal.Record(warp_v1_, warp_v3_); 541 | cal_save_flag_ = true; 542 | } 543 | 544 | /** @brief Sets the calibration data for 1V/Octave over Warp CV 545 | * typically set after reading stored data from external memory. 546 | */ 547 | inline void SetWarpCalData(float scale, float offset) 548 | { 549 | voct_cal.SetData(scale, offset); 550 | } 551 | 552 | /** @brief Gets the current calibration data for 1V/Octave over Warp CV 553 | * typically used to prepare data for storing after successful calibration 554 | */ 555 | inline void GetWarpCalData(float &scale, float &offset) 556 | { 557 | voct_cal.GetData(scale, offset); 558 | } 559 | 560 | /** @brief Sets the cv offset from an externally array of data */ 561 | inline void SetCvOffsetData(float *data) 562 | { 563 | for(int i = 0; i < CV_LAST; i++) 564 | { 565 | cv_offsets_[i] = data[i]; 566 | } 567 | } 568 | 569 | /** @brief Fills an array with the offset data currently being used */ 570 | inline void GetCvOffsetData(float *data) 571 | { 572 | for(int i = 0; i < CV_LAST; i++) 573 | { 574 | data[i] = cv_offsets_[i]; 575 | } 576 | } 577 | 578 | /** @brief Checks to see if calibration has been completed and needs to be saved */ 579 | inline bool ReadyToSaveCal() const { return cal_save_flag_; } 580 | 581 | /** @brief signal the cal-save flag to clear once calibration data has been written to ext. flash memory */ 582 | inline void ClearSaveCalFlag() { cal_save_flag_ = false; } 583 | 584 | /** Array of CV inputs */ 585 | daisy::AnalogControl cv[CV_LAST]; 586 | 587 | /** Array of momentary switches */ 588 | daisy::Switch switches[SW_LAST]; 589 | 590 | /** Array of gate inputs */ 591 | daisy::GateIn gates[GATE_LAST]; 592 | 593 | /** Array of knob controls */ 594 | daisy::AnalogControl controls[KNOB_LAST]; 595 | 596 | /** Daisy Seed base object */ 597 | daisy::DaisySeed seed; 598 | 599 | private: 600 | /** @brief Address offset where calibration data is stored in the Aurora 601 | * default firmware. 602 | * This data is calibrated from the Qu-Bit default Aurora firmware. 603 | * It is advised not to save over this data unless you are prepared to recalibrate. 604 | */ 605 | static constexpr uint32_t kCalibrationDataOffset = 4096; 606 | 607 | /** @brief Internal struct for managing LED indices */ 608 | struct LedIdx 609 | { 610 | int8_t r; 611 | int8_t g; 612 | int8_t b; 613 | }; 614 | 615 | /** @brief internal mapping for LED routing on hardware */ 616 | const LedIdx LedMap[LED_LAST] = {{0, 1, 2}, 617 | {3, 4, 5}, 618 | {6, 7, 8}, 619 | {9, 10, 11}, 620 | {16, 17, 18}, 621 | {12, 13, 14}, 622 | {22, 23, 24}, 623 | {28, 29, 30}, 624 | {-1, 20, 21}, 625 | {-1, 26, 27}, 626 | {-1, 19, 25}}; 627 | 628 | /** @brief Specifiers for hardware revisions */ 629 | enum class HardwareVersion 630 | { 631 | REV3, /**< Last version using normal Daisy Seed + PCM3060 */ 632 | REV4, /**< s2dfm */ 633 | }; 634 | 635 | 636 | /** @brief LED driver object for internal use */ 637 | daisy::LedDriverPca9685<2, true> led_driver_; 638 | 639 | /** @brief tracking current callback for recovery after samplerate change */ 640 | daisy::AudioHandle::AudioCallback current_cb_; 641 | 642 | daisy::TimerHandle tim5_handle; 643 | daisy::I2CHandle i2c; 644 | daisy::Pcm3060 codec; 645 | 646 | HardwareVersion hw_version; 647 | 648 | /** Cal data */ 649 | float warp_v1_, warp_v3_; 650 | daisy::VoctCalibration voct_cal; 651 | float cv_offsets_[CV_LAST]; 652 | 653 | bool cal_save_flag_; 654 | 655 | 656 | void ConfigureAudio() 657 | { 658 | /** Only need to set up any specific audio when dealing with the rev3 hardware */ 659 | if(hw_version == HardwareVersion::REV3) 660 | { 661 | daisy::I2CHandle::Config codec_i2c_config; 662 | codec_i2c_config.periph 663 | = daisy::I2CHandle::Config::Peripheral::I2C_1; 664 | codec_i2c_config.pin_config = {seed.GetPin(11), seed.GetPin(12)}; 665 | codec_i2c_config.speed 666 | = daisy::I2CHandle::Config::Speed::I2C_400KHZ; 667 | codec_i2c_config.mode = daisy::I2CHandle::Config::Mode::I2C_MASTER; 668 | 669 | i2c.Init(codec_i2c_config); 670 | codec.Init(i2c); 671 | 672 | daisy::SaiHandle::Config sai_cfg; 673 | sai_cfg.periph = daisy::SaiHandle::Config::Peripheral::SAI_2; 674 | sai_cfg.sr = daisy::SaiHandle::Config::SampleRate::SAI_48KHZ; 675 | sai_cfg.bit_depth = daisy::SaiHandle::Config::BitDepth::SAI_24BIT; 676 | sai_cfg.a_sync = daisy::SaiHandle::Config::Sync::SLAVE; 677 | sai_cfg.b_sync = daisy::SaiHandle::Config::Sync::MASTER; 678 | sai_cfg.a_dir = daisy::SaiHandle::Config::Direction::RECEIVE; 679 | sai_cfg.b_dir = daisy::SaiHandle::Config::Direction::TRANSMIT; 680 | sai_cfg.pin_config.fs = seed.GetPin(27); 681 | sai_cfg.pin_config.mclk = seed.GetPin(24); 682 | sai_cfg.pin_config.sck = seed.GetPin(28); 683 | sai_cfg.pin_config.sa = seed.GetPin(26); 684 | sai_cfg.pin_config.sb = seed.GetPin(25); 685 | // Then Initialize 686 | daisy::SaiHandle sai_2_handle; 687 | sai_2_handle.Init(sai_cfg); 688 | daisy::AudioHandle::Config audio_cfg; 689 | audio_cfg.blocksize = 48; 690 | audio_cfg.samplerate 691 | = daisy::SaiHandle::Config::SampleRate::SAI_48KHZ; 692 | audio_cfg.postgain = 1.0f; 693 | seed.audio_handle.Init(audio_cfg, sai_2_handle); 694 | } 695 | else 696 | { 697 | /** Set 44.1kHz Deemp Filter pin on Daisy Seed2 DFM to off */ 698 | daisy::GPIO deemp; 699 | daisy::Pin deemp_pin(daisy::PORTB, 11); 700 | deemp.Init(deemp_pin, daisy::GPIO::Mode::OUTPUT); 701 | deemp.Write(0); 702 | /** Adjust audio output to match input signal level */ 703 | seed.audio_handle.SetOutputCompensation(0.8785f); 704 | } 705 | } 706 | 707 | // pots,switches, gates, CV 708 | void ConfigureControls() 709 | { 710 | if(hw_version == HardwareVersion::REV3) 711 | { 712 | // ===== CV and Pots (adc) ===== 713 | daisy::AdcChannelConfig cfg[CV_LAST + 1]; 714 | 715 | for(int i = 0; i < CV_LAST; i++) 716 | { 717 | cfg[i].InitSingle(seed.GetPin(17 + i)); 718 | } 719 | cfg[CV_LAST].InitMux(seed.GetPin(16), 720 | 6, 721 | seed.GetPin(7), 722 | seed.GetPin(8), 723 | seed.GetPin(9)); 724 | seed.adc.Init(cfg, CV_LAST + 1); 725 | 726 | // init cv as bipolar analog controls 727 | for(size_t i = 0; i < CV_LAST; i++) 728 | { 729 | cv[i].InitBipolarCv(seed.adc.GetPtr(i), AudioCallbackRate()); 730 | } 731 | 732 | // init pots as analog controls 733 | for(size_t i = 0; i < KNOB_LAST; i++) 734 | { 735 | controls[i].Init(seed.adc.GetMuxPtr(CV_LAST, i), 736 | AudioCallbackRate()); 737 | } 738 | 739 | // ===== gates ===== 740 | dsy_gpio_pin pin; 741 | pin = seed.GetPin(23); 742 | gates[GATE_FREEZE].Init(&pin); 743 | } 744 | else 745 | { 746 | /** For the new version we have a different ADC layout */ 747 | daisy::AdcChannelConfig cfg[CV_LAST + KNOB_LAST]; 748 | /** Read CVs then Pots for best consistency with last version */ 749 | cfg[CV_LAST + KNOB_TIME].InitSingle(daisy::seed::A0); 750 | cfg[CV_LAST + KNOB_REFLECT].InitSingle(daisy::seed::A1); 751 | cfg[CV_ATMOSPHERE].InitSingle(daisy::seed::A2); 752 | cfg[CV_TIME].InitSingle(daisy::seed::A3); 753 | cfg[CV_MIX].InitSingle(daisy::seed::A4); 754 | cfg[CV_REFLECT].InitSingle(daisy::seed::A5); 755 | cfg[CV_BLUR].InitSingle(daisy::seed::A6); 756 | cfg[CV_WARP].InitSingle(daisy::seed::A7); 757 | cfg[CV_LAST + KNOB_MIX].InitSingle(daisy::seed::A8); 758 | cfg[CV_LAST + KNOB_WARP].InitSingle(daisy::seed::A9); 759 | cfg[CV_LAST + KNOB_ATMOSPHERE].InitSingle(daisy::seed::A10); 760 | cfg[CV_LAST + KNOB_BLUR].InitSingle(daisy::seed::A11); 761 | seed.adc.Init(cfg, CV_LAST + KNOB_LAST); 762 | 763 | for(size_t i = 0; i < CV_LAST; i++) 764 | { 765 | cv[i].InitBipolarCv(seed.adc.GetPtr(i), AudioCallbackRate()); 766 | } 767 | // init pots as analog controls with offset to last CV for ptr 768 | for(size_t i = 0; i < KNOB_LAST; i++) 769 | { 770 | controls[i].Init(seed.adc.GetPtr(CV_LAST + i), 771 | AudioCallbackRate()); 772 | } 773 | dsy_gpio_pin freeze_pin = daisy::seed::D26; 774 | gates[GATE_FREEZE].Init(&freeze_pin); 775 | } 776 | // And anything generic to both versions 777 | // ===== switches ===== 778 | switches[SW_FREEZE].Init(seed.GetPin(1), AudioCallbackRate()); 779 | switches[SW_REVERSE].Init(seed.GetPin(10), AudioCallbackRate()); 780 | switches[SW_SHIFT].Init(seed.GetPin(13), AudioCallbackRate()); 781 | 782 | dsy_gpio_pin revpin = seed.GetPin(14); 783 | gates[GATE_REVERSE].Init(&revpin); 784 | } 785 | 786 | void ConfigureLeds() 787 | { 788 | // reinit i2c at 1MHz 789 | daisy::I2CHandle::Config codec_i2c_config; 790 | codec_i2c_config.periph = daisy::I2CHandle::Config::Peripheral::I2C_1; 791 | codec_i2c_config.pin_config = {seed.GetPin(11), seed.GetPin(12)}; 792 | codec_i2c_config.speed = daisy::I2CHandle::Config::Speed::I2C_1MHZ; 793 | codec_i2c_config.mode = daisy::I2CHandle::Config::Mode::I2C_MASTER; 794 | i2c.Init(codec_i2c_config); 795 | 796 | // init driver 797 | led_driver_.Init(i2c, {0x00, 0x01}, led_dma_buffer_a, led_dma_buffer_b); 798 | 799 | ClearLeds(); 800 | WriteLeds(); 801 | } 802 | 803 | HardwareVersion GetBoardRevision() 804 | { 805 | daisy::GPIO version_gpio; 806 | version_gpio.Init(daisy::seed::D2, 807 | daisy::GPIO::Mode::INPUT, 808 | daisy::GPIO::Pull::PULLUP); 809 | /** Read High means rev3 otherwise rev4 */ 810 | auto pinstate = version_gpio.Read(); 811 | version_gpio.DeInit(); 812 | if(pinstate) 813 | return HardwareVersion::REV3; 814 | else 815 | return HardwareVersion::REV4; 816 | } 817 | 818 | /** @brief Loads and sets calibration data */ 819 | void LoadCalibrationData() 820 | { 821 | daisy::PersistentStorage cal_storage(seed.qspi); 822 | CalibrationData default_cal; 823 | cal_storage.Init(default_cal, kCalibrationDataOffset); 824 | auto &cal_data = cal_storage.GetSettings(); 825 | SetWarpCalData(cal_data.warp_scale, cal_data.warp_offset); 826 | SetCvOffsetData(cal_data.cv_offset); 827 | } 828 | }; 829 | 830 | } // namespace aurora 831 | 832 | #endif 833 | -------------------------------------------------------------------------------- /rebuild_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./ci/build_libs.sh 4 | ./ci/build_examples.py 5 | 6 | echo "finished" 7 | 8 | --------------------------------------------------------------------------------