├── .gitmodules ├── .gitignore ├── src ├── droplets │ ├── template_droplet.cpp │ ├── droplet_manager.cpp │ ├── noise_droplet.cpp │ ├── droplet_manager.h │ ├── noise_droplet.h │ ├── mixer_droplet.h │ ├── template_droplet.h │ ├── vca_droplet.h │ ├── ladder_filter_droplet.h │ ├── droplet.cpp │ ├── vco_droplet.h │ ├── ladder_filter_droplet.cpp │ ├── sequencer_droplet.h │ ├── vca_droplet.cpp │ ├── lfo_droplet.h │ ├── mixer_droplet.cpp │ ├── lfo_droplet.cpp │ ├── ad_droplet.h │ ├── vco_droplet.cpp │ ├── droplet.h │ ├── sequencer_droplet.cpp │ └── ad_droplet.cpp ├── graphics │ ├── wave.h │ ├── wave.cpp │ ├── graph.cpp │ ├── graph.h │ ├── sprite.cpp │ └── sprite.h ├── main.h ├── menu.h ├── menu_item.cpp ├── menu_item.h ├── menu.cpp ├── main.cpp ├── util.cpp └── util.h ├── Makefile ├── .github └── workflows │ └── build-binary.yml ├── LICENSE ├── create-new-droplet.sh └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libDaisy"] 2 | path = lib/libDaisy 3 | url = git@github.com:electro-smith/libDaisy.git 4 | [submodule "daisySP"] 5 | path = lib/daisySP 6 | url = git@github.com:electro-smith/DaisySP.git 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Daisy 2 | build/ 3 | 4 | # Prerequisites 5 | *.d 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | *.smod 25 | 26 | # Compiled Static libraries 27 | *.lai 28 | *.la 29 | *.a 30 | *.lib 31 | 32 | # Executables 33 | *.exe 34 | *.out 35 | *.app 36 | -------------------------------------------------------------------------------- /src/droplets/template_droplet.cpp: -------------------------------------------------------------------------------- 1 | #include "template_droplet.h" 2 | 3 | TemplateDroplet::TemplateDroplet(DaisyPatch* m_patch, 4 | DropletState m_state, 5 | float sample_rate) : 6 | Droplet(m_patch, 7 | m_state) { 8 | } 9 | 10 | TemplateDroplet::~TemplateDroplet() {} 11 | 12 | void TemplateDroplet::Control() {} 13 | 14 | void TemplateDroplet::Process(AudioHandle::InputBuffer in, 15 | AudioHandle::OutputBuffer out, 16 | size_t size) { 17 | } 18 | 19 | void TemplateDroplet::Draw() { 20 | DrawName("Template"); 21 | } 22 | 23 | void TemplateDroplet::UpdateStateCallback() {} 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET = droplets 2 | 3 | SRC_DIR := ./src 4 | CPP_SOURCES := $(wildcard $(SRC_DIR)/*.cpp $(SRC_DIR)/*/*.cpp) 5 | 6 | LIBDAISY_DIR = ./lib/libDaisy 7 | DAISYSP_DIR = ./lib/daisySP 8 | 9 | # Reduce binary size 10 | OPT += -Os 11 | 12 | SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core 13 | include $(SYSTEM_FILES_DIR)/Makefile 14 | 15 | libdaisy: 16 | cd $(LIBDAISY_DIR) && make 17 | daisysp: 18 | cd $(DAISYSP_DIR) && make 19 | lib: libdaisy daisysp 20 | deploy: lib all program 21 | pulllibs: 22 | cd $(LIBDAISY_DIR) && make clean && git pull origin master 23 | cd $(DAISYSP_DIR) && make clean && git pull origin master 24 | updatelibs: pulllibs lib 25 | -------------------------------------------------------------------------------- /src/droplets/droplet_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "droplet_manager.h" 2 | 3 | void DropletManager::ToggleSplit() { 4 | split = !split; 5 | } 6 | 7 | bool DropletManager::GetSplitMode() { 8 | return split; 9 | } 10 | 11 | void DropletManager::SetSelected(DropletState state) { 12 | selected_drop = state; 13 | } 14 | 15 | DropletState DropletManager::GetSelected() { 16 | return selected_drop; 17 | } 18 | 19 | std::string DropletManager::OtherStateName(DropletState state) { 20 | switch (state) { 21 | case DropletState::kLeft: 22 | return "Right"; 23 | case DropletState::kRight: 24 | return "Left"; 25 | case DropletState::kFull: 26 | default: 27 | return ""; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/build-binary.yml: -------------------------------------------------------------------------------- 1 | name: Build Binary 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 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: make lib 22 | - name: Build 23 | run: make 24 | - name: Archive 25 | uses: actions/upload-artifact@v1 26 | with: 27 | name: droplets.bin 28 | path: build/droplets.bin -------------------------------------------------------------------------------- /src/graphics/wave.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef CASCADE_GRAPHICS_WAVE_H_ 4 | #define CASCADE_GRAPHICS_WAVE_H_ 5 | 6 | #include "sprite.h" 7 | 8 | enum class WaveShape {kSine, kTriangle, kSaw, kSquare, kRamp}; 9 | 10 | class Wave: public Sprite { 11 | private: 12 | WaveShape wave; 13 | const double pi = std::acos(-1); 14 | 15 | /* 16 | * Changes pixels of the graphic based on the set wave shape. 17 | */ 18 | void DrawShape(); 19 | public: 20 | /* 21 | * Contstructor for wave shape sprite. 22 | * 23 | * @param m_wave wave shape 24 | * @param width sprite width 25 | * @param height sprite height 26 | */ 27 | Wave(WaveShape m_wave, 28 | int width, 29 | int height); 30 | 31 | /* 32 | * Sets the sprites wave shape. 33 | * 34 | * @param m_wave wave shape 35 | */ 36 | void SetWaveShape(WaveShape m_wave); 37 | }; 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /src/droplets/noise_droplet.cpp: -------------------------------------------------------------------------------- 1 | #include "noise_droplet.h" 2 | 3 | NoiseDroplet::NoiseDroplet(DaisyPatch* m_patch, 4 | DropletState m_state) : 5 | Droplet(m_patch, 6 | m_state) { 7 | noise.Init(); 8 | } 9 | 10 | void NoiseDroplet::Control() {} 11 | 12 | void NoiseDroplet::Process(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { 13 | for (size_t i = 0; i < size; i++) { 14 | float sig = noise.Process(); 15 | for (size_t chn = GetChannelMin(); chn < GetChannelMax(); chn++) { 16 | out[chn][i] = sig; 17 | } 18 | } 19 | } 20 | 21 | void NoiseDroplet::Draw() { 22 | for (int h = 0; h < GetTitleHeight(); h++) { 23 | for (int w = GetScreenMin(); w < GetScreenMax(); w++) { 24 | Patch()->display.DrawPixel(w, h, rand() % 15 == 0); 25 | } 26 | } 27 | DrawName("Noise"); 28 | } 29 | 30 | void NoiseDroplet::UpdateStateCallback() {} 31 | 32 | void NoiseDroplet::SetControls() {} 33 | -------------------------------------------------------------------------------- /src/droplets/droplet_manager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_DROPLET_MANAGER_H_ 4 | #define DROPLETS_DROPLET_MANAGER_H_ 5 | 6 | #include "droplet.h" 7 | 8 | class DropletManager { 9 | private: 10 | bool split = false; 11 | DropletState selected_drop = DropletState::kFull; 12 | public: 13 | /* 14 | * Toggle droplet split mode. 15 | */ 16 | void ToggleSplit(); 17 | 18 | /* 19 | * Droplet split mode. 20 | * 21 | * @return droplet split mode state 22 | */ 23 | bool GetSplitMode(); 24 | 25 | /* 26 | * Set the currently selected droplet. 27 | * 28 | * @param selected droplet 29 | */ 30 | void SetSelected(DropletState state); 31 | 32 | /* 33 | * Get the currently selected droplet. 34 | * 35 | * @return selected droplet 36 | */ 37 | DropletState GetSelected(); 38 | 39 | /* 40 | * Returns the name of the other droplet 41 | * 42 | * @param state droplat state of the current 43 | * @return other droplet name 44 | */ 45 | std::string OtherStateName(DropletState state); 46 | }; 47 | 48 | #endif // DROPLETS_DROPLET_MANAGER_H_ 49 | 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Christian Colglazier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /create-new-droplet.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function printHelp { 4 | echo 'This bash script will create a new droplet. Please input the name for the droplet in PascalCase as a command line argument.' 5 | } 6 | 7 | # Check for arguments 8 | if [ $# -eq 0 ] 9 | then 10 | printHelp 11 | fi 12 | 13 | # Setup Droplet names 14 | dropletName=$1 15 | dropletNameWords=$(echo $dropletName | sed 's/\([A-Z][^A-Z]\)/ \1/g') 16 | upper=$(echo ${dropletNameWords} | tr '[:lower:]' '[:upper:]') 17 | upper="${upper// /_}" 18 | lower=$(echo ${upper} | tr '[:upper:]' '[:lower:]') 19 | 20 | # Create copy from template 21 | folder=$(dirname "$0") 22 | headerFile="${folder}/src/droplets/${lower}_droplet.h" 23 | cppFile="${folder}/src/droplets/${lower}_droplet.cpp" 24 | cp ${folder}/src/droplets/template_droplet.cpp ${cppFile} 25 | cp ${folder}/src/droplets/template_droplet.h ${headerFile} 26 | 27 | # Replace 28 | sed -i "s/Template/${dropletName}/g" ${headerFile} 29 | sed -i "s/Template/${dropletName}/g" ${cppFile} 30 | sed -i "s/template/${lower}/g" ${headerFile} 31 | sed -i "s/template/${lower}/g" ${cppFile} 32 | sed -i "s/TEMPLATE/${upper}/g" ${headerFile} 33 | sed -i "s/TEMPLATE/${upper}/g" ${cppFile} 34 | -------------------------------------------------------------------------------- /src/graphics/wave.cpp: -------------------------------------------------------------------------------- 1 | #include "wave.h" 2 | 3 | Wave::Wave(WaveShape m_wave, 4 | int width, 5 | int height) : Sprite(width, height) { 6 | wave = m_wave; 7 | DrawShape(); 8 | } 9 | 10 | void Wave::DrawShape() { 11 | SetBlank(); 12 | int mid = GetWidth()/2; 13 | int x_max = GetWidth()-1; 14 | int y_max = GetHeight()-1; 15 | switch(wave) { 16 | case WaveShape::kSaw: 17 | AddLine(0, 0, x_max, y_max, true); 18 | AddLine(x_max, y_max, x_max, 0, true); 19 | return; 20 | case WaveShape::kSquare: 21 | AddLine(0, 0, mid, 0, true); 22 | AddLine(mid, 0, mid, y_max, true); 23 | AddLine(mid, y_max, x_max, y_max, true); 24 | AddLine(GetWidth()-1, GetHeight()-1, GetWidth()-1, 0, true); 25 | return; 26 | case WaveShape::kTriangle: 27 | AddLine(0, 0, mid, y_max, true); 28 | AddLine(mid, y_max, x_max, 0, true); 29 | return; 30 | case WaveShape::kSine: 31 | default: 32 | for (int i = 0; i < GetWidth(); i++) { 33 | int pixel = (int) round(std::sin(2*pi*((double)(i%x_max)/x_max)) * (y_max/2) + y_max/2); 34 | AddPixel(i, pixel, true); 35 | } 36 | return; 37 | } 38 | } 39 | 40 | void Wave::SetWaveShape(WaveShape m_wave) { 41 | wave = m_wave; 42 | DrawShape(); 43 | } 44 | -------------------------------------------------------------------------------- /src/droplets/noise_droplet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_NOISE_DROPLET_H_ 4 | #define DROPLETS_NOISE_DROPLET_H_ 5 | 6 | #include "daisysp.h" 7 | #include "daisy_patch.h" 8 | 9 | #include "droplet.h" 10 | #include "../util.h" 11 | 12 | class NoiseDroplet: public Droplet { 13 | private: 14 | daisysp::WhiteNoise noise; 15 | public: 16 | /* 17 | * Constructor for a droplet which outputs noise. 18 | * 19 | * @param m_patch pointer to patch 20 | * @param m_state droplet position 21 | */ 22 | NoiseDroplet(DaisyPatch*, DropletState); 23 | 24 | /* 25 | * Processes user controls and inputs. 26 | */ 27 | void Control(); 28 | 29 | /* 30 | * Processes audio input and outputs. 31 | * 32 | * @param in the audio inputs for the patch 33 | * @param out the audio outputs for the patch 34 | * @param size the number of inputs and outputs 35 | */ 36 | void Process(AudioHandle::InputBuffer in, 37 | AudioHandle::OutputBuffer out, 38 | size_t size); 39 | 40 | /* 41 | * Processes information to be shown on the display. 42 | */ 43 | void Draw(); 44 | 45 | /* 46 | * Runs when droplet state is updated. 47 | */ 48 | void UpdateStateCallback(); 49 | 50 | /* 51 | * Set up the controls for the droplet. 52 | */ 53 | void SetControls(); 54 | }; 55 | 56 | #endif // DROPLETS_NOISE_DROPLET_H_ 57 | -------------------------------------------------------------------------------- /src/droplets/mixer_droplet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_MIXER_DROPLET_H_ 4 | #define DROPLETS_MIXER_DROPLET_H_ 5 | 6 | #include "daisysp.h" 7 | #include "daisy_patch.h" 8 | 9 | #include "droplet.h" 10 | #include "../util.h" 11 | 12 | using namespace daisy; 13 | using namespace daisysp; 14 | 15 | class MixerDroplet: public Droplet { 16 | private: 17 | Parameter mix[4]; 18 | 19 | public: 20 | /* 21 | * Constructor for a mixer droplet. 22 | * 23 | * @param m_patch pointer to patch 24 | * @param m_state droplet position 25 | */ 26 | MixerDroplet(DaisyPatch* m_patch, 27 | DropletState m_state); 28 | 29 | /* 30 | * Destructor for vco droplet. 31 | */ 32 | ~MixerDroplet(); 33 | 34 | /* 35 | * Processes user controls and inputs. 36 | */ 37 | void Control(); 38 | 39 | /* 40 | * Processes audio input and outputs. 41 | * 42 | * @param in the audio inputs for the patch 43 | * @param out the audio outputs for the patch 44 | * @param size the number of inputs and outputs 45 | */ 46 | void Process(AudioHandle::InputBuffer in, 47 | AudioHandle::OutputBuffer out, 48 | size_t size); 49 | 50 | /* 51 | * Processes information to be shown on the display. 52 | */ 53 | void Draw(); 54 | 55 | /* 56 | * Runs when droplet state is updated. 57 | */ 58 | void UpdateStateCallback(); 59 | 60 | /* 61 | * Set up the controls for the droplet. 62 | */ 63 | void SetControls(); 64 | }; 65 | 66 | #endif // DROPLETS_VCA_DROPLET_H_ 67 | -------------------------------------------------------------------------------- /src/droplets/template_droplet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_TEMPLATE_DROPLET_H_ 4 | #define DROPLETS_TEMPLATE_DROPLET_H_ 5 | 6 | #include "daisysp.h" 7 | #include "daisy_patch.h" 8 | 9 | #include "droplet.h" 10 | #include "../util.h" 11 | 12 | class TemplateDroplet: public Droplet { 13 | private: 14 | public: 15 | /* 16 | * Constructor for a droplet. 17 | * 18 | * @param m_patch pointer to patch 19 | * @param m_state droplet position 20 | * @param sample_rate audio sample rate 21 | */ 22 | TemplateDroplet(DaisyPatch* m_patch, 23 | DropletState m_state, 24 | float sample_rate); 25 | 26 | /* 27 | * Destructor for template droplet. 28 | */ 29 | ~TemplateDroplet(); 30 | 31 | /* 32 | * Processes user controls and inputs. 33 | */ 34 | void Control(); 35 | 36 | /* 37 | * Processes audio input and outputs. 38 | * 39 | * @param in the audio inputs for the patch 40 | * @param out the audio outputs for the patch 41 | * @param size the number of inputs and outputs 42 | */ 43 | void Process(AudioHandle::InputBuffer in, 44 | AudioHandle::OutputBuffer out, 45 | size_t size); 46 | 47 | /* 48 | * Processes information to be shown on the display. 49 | */ 50 | void Draw(); 51 | 52 | /* 53 | * Runs when droplet state is updated. 54 | */ 55 | void UpdateStateCallback(); 56 | 57 | /* 58 | * Set up the controls for the droplet. 59 | */ 60 | void SetControls(); 61 | }; 62 | 63 | #endif // DROPLETS_TEMPLATE_DROPLET_H_ 64 | -------------------------------------------------------------------------------- /src/droplets/vca_droplet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_VCA_DROPLET_H_ 4 | #define DROPLETS_VCA_DROPLET_H_ 5 | 6 | #include "daisysp.h" 7 | #include "daisy_patch.h" 8 | 9 | #include "droplet.h" 10 | #include "../util.h" 11 | 12 | using namespace daisy; 13 | using namespace daisysp; 14 | 15 | class VCADroplet: public Droplet { 16 | private: 17 | Parameter vca[4]; 18 | 19 | public: 20 | /* 21 | * Constructor for voltage amplifier droplet. 22 | * 23 | * @param m_patch pointer to patch 24 | * @param m_state droplet position 25 | */ 26 | VCADroplet(DaisyPatch* m_patch, 27 | DropletState m_state); 28 | 29 | /* 30 | * Destructor for vco droplet. 31 | */ 32 | ~VCADroplet(); 33 | 34 | /* 35 | * Processes user controls and inputs. 36 | */ 37 | void Control(); 38 | 39 | /* 40 | * Processes audio input and outputs. 41 | * 42 | * @param in the audio inputs for the patch 43 | * @param out the audio outputs for the patch 44 | * @param size the number of inputs and outputs 45 | */ 46 | void Process(AudioHandle::InputBuffer in, 47 | AudioHandle::OutputBuffer out, 48 | size_t size); 49 | 50 | /* 51 | * Processes information to be shown on the display. 52 | */ 53 | void Draw(); 54 | 55 | /* 56 | * Runs when droplet state is updated. 57 | */ 58 | void UpdateStateCallback(); 59 | 60 | /* 61 | * Set up the controls for the droplet. 62 | */ 63 | void SetControls(); 64 | }; 65 | 66 | #endif // DROPLETS_VCA_DROPLET_H_ 67 | -------------------------------------------------------------------------------- /src/graphics/graph.cpp: -------------------------------------------------------------------------------- 1 | #include "graph.h" 2 | 3 | Graph::Graph(int m_width, int m_height) { 4 | width = m_width; 5 | height = m_height; 6 | graph = new bool*[width]; 7 | for (int w = 0; w < width; w++) { 8 | graph[w] = new bool[height]; 9 | for (int h = 0; h < height; h++) { 10 | graph[w][h] = false; 11 | } 12 | } 13 | active = 0; 14 | } 15 | 16 | Graph::~Graph() { 17 | for (int w = 0; w < width; w++) { 18 | delete[] graph[w]; 19 | } 20 | delete[] graph; 21 | } 22 | 23 | int Graph::GetNextActive() { 24 | if (active == width - 1) { 25 | return 0; 26 | } else { 27 | return active+1; 28 | } 29 | } 30 | 31 | void Graph::Update() { 32 | active = GetNextActive(); 33 | ClearColumn(); 34 | } 35 | 36 | void Graph::ClearColumn() { 37 | for (int h = 0; h < height; h++) { 38 | graph[active][h] = false; 39 | } 40 | } 41 | 42 | void Graph::SetPixel(int pos) { 43 | SetPixel(pos, true); 44 | } 45 | 46 | void Graph::SetPixel(int pos, bool on) { 47 | graph[active][pos] = on; 48 | } 49 | 50 | void Graph::SetPixelPercentage(double percentage) { 51 | SetPixelPercentage(percentage, true); 52 | } 53 | 54 | void Graph::SetPixelPercentage(double percentage, bool on) { 55 | SetPixel((int)(percentage*height), on); 56 | } 57 | 58 | void Graph::Draw(DaisyPatch* patch, int x, int y) { 59 | for (int w = 0; w < width; w++) { 60 | for (int h = 0; h < height; h++) { 61 | patch->display.DrawPixel(x+w, y+h, 62 | graph[GetShiftArray(w, active+1, width)] 63 | [GetShiftArray(height-h, height-1, height)]); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/droplets/ladder_filter_droplet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_LADDER_FILTER_DROPLET_H_ 4 | #define DROPLETS_LADDER_FILTER_DROPLET_H_ 5 | 6 | #include "daisysp.h" 7 | #include "daisy_patch.h" 8 | 9 | #include "droplet.h" 10 | #include "../util.h" 11 | 12 | class LadderFilterDroplet: public Droplet { 13 | private: 14 | MoogLadder filter[4]; 15 | Parameter freq_ctrl, res_ctrl; 16 | float freq, res; 17 | float FILTER_MAX = 10000.0f; 18 | float RES_MAX = 0.95f; 19 | int res_points[6]; 20 | public: 21 | /* 22 | * Constructor for a droplet. 23 | * 24 | * @param m_patch pointer to patch 25 | * @param m_state droplet position 26 | * @param sample_rate audio sample rate 27 | */ 28 | LadderFilterDroplet(DaisyPatch* m_patch, 29 | DropletState m_state, 30 | float sample_rate); 31 | 32 | /* 33 | * Destructor for ladder filter droplet. 34 | */ 35 | ~LadderFilterDroplet(); 36 | 37 | /* 38 | * Processes user controls and inputs. 39 | */ 40 | void Control(); 41 | 42 | /* 43 | * Processes audio input and outputs. 44 | * 45 | * @param in the audio inputs for the patch 46 | * @param out the audio outputs for the patch 47 | * @param size the number of inputs and outputs 48 | */ 49 | void Process(AudioHandle::InputBuffer in, 50 | AudioHandle::OutputBuffer out, 51 | size_t size); 52 | 53 | /* 54 | * Processes information to be shown on the display. 55 | */ 56 | void Draw(); 57 | 58 | /* 59 | * Runs when droplet state is updated. 60 | */ 61 | void UpdateStateCallback(); 62 | 63 | /* 64 | * Set up the controls for the droplet. 65 | */ 66 | void SetControls(); 67 | }; 68 | 69 | #endif // DROPLETS_LADDER_FILTER_DROPLET_H_ 70 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_MAIN_H_ 4 | #define DROPLETS_MAIN_H_ 5 | 6 | #include "daisysp.h" 7 | #include "daisy_patch.h" 8 | 9 | #include 10 | 11 | #include "util.h" 12 | #include "menu.h" 13 | #include "droplets/droplet.h" 14 | #include "droplets/droplet_manager.h" 15 | #include "droplets/ad_droplet.h" 16 | #include "droplets/ladder_filter_droplet.h" 17 | #include "droplets/lfo_droplet.h" 18 | #include "droplets/mixer_droplet.h" 19 | #include "droplets/noise_droplet.h" 20 | #include "droplets/sequencer_droplet.h" 21 | #include "droplets/vca_droplet.h" 22 | #include "droplets/vco_droplet.h" 23 | 24 | DaisyPatch patch; 25 | DropletManager* manager = new DropletManager(); 26 | Menu* selected_menu; 27 | Menu* left_menu = new Menu(&patch, DropletState::kLeft, manager); 28 | Menu* right_menu = new Menu(&patch, DropletState::kRight, manager); 29 | Droplet* droplet_left; 30 | Droplet* droplet_right; 31 | float sample_rate; 32 | 33 | /* 34 | * Processes user controls and inputs. 35 | */ 36 | void ProcessControls(); 37 | 38 | /* 39 | * Processes information to be displayed on the screen. 40 | */ 41 | void ProcessOled(); 42 | 43 | /* 44 | * Processes patch outputs. 45 | */ 46 | void ProcessOutputs(); 47 | 48 | /* 49 | * Processes audio input and outputs with a faster and higher priority control 50 | * loop. 51 | * 52 | * @param in the audio inputs for the patch 53 | * @param out the audio outputs for the patch 54 | * @param size the number of inputs and outputs 55 | */ 56 | static void AudioThrough(AudioHandle::InputBuffer in, 57 | AudioHandle::OutputBuffer out, 58 | size_t size); 59 | 60 | /* 61 | * Initializes a new audio processing droplet based on menu state. 62 | * 63 | * @param state new droplet state 64 | * @param menu menu state 65 | * @return droplet 66 | */ 67 | Droplet* GetDroplet(DropletState state, MenuState menu); 68 | 69 | #endif // DROPLETS_MAIN_H_ 70 | -------------------------------------------------------------------------------- /src/graphics/graph.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_GRAPHICS_GRAPH_H 4 | #define DROPLETS_GRAPHICS_GRAPH_H 5 | 6 | #include "daisy_patch.h" 7 | #include "../util.h" 8 | 9 | using namespace daisy; 10 | 11 | class Graph { 12 | private: 13 | int width, height, active; 14 | bool** graph; 15 | int GetNextActive(); 16 | public: 17 | /* 18 | * Contsturctor for graph. 19 | * 20 | * @param m_width width 21 | * @param m_height height 22 | */ 23 | Graph(int m_width, 24 | int m_height); 25 | /* 26 | * Destructor for graph. 27 | */ 28 | ~Graph(); 29 | 30 | /* 31 | * Shifts the graph by one pixel. 32 | */ 33 | void Update(); 34 | 35 | /* 36 | * Clears the pxiels at the currently active column. 37 | */ 38 | void ClearColumn(); 39 | 40 | /* 41 | * Draws an active pixel on the graph. 42 | * 43 | * @param pos y dimension of pixel 44 | */ 45 | void SetPixel(int pos); 46 | 47 | /* 48 | * Draws a pixel on the graph. 49 | * 50 | * @param pos y dimension of pixel 51 | * @param on pixel active state 52 | */ 53 | void SetPixel(int pos, bool on); 54 | 55 | /* 56 | * Draws an acive pixel based on a percentage of the 57 | * the height of the graph. 58 | * 59 | * @param percentage precentage height of the pixel 60 | */ 61 | void SetPixelPercentage(double percentage); 62 | 63 | /* 64 | * Draws a pixel based on a percentage of the 65 | * the height of the graph. 66 | * 67 | * @param percentage precentage height of the pixel 68 | * @param on pixel active state 69 | */ 70 | void SetPixelPercentage(double percentage, bool on); 71 | 72 | /* 73 | * Draws graph on display. 74 | * 75 | * @param patch daisy patch board 76 | * @param x starting x coordinate of graph 77 | * @param y starting y coordinate of graph 78 | */ 79 | void Draw(DaisyPatch* patch, int x, int y); 80 | }; 81 | 82 | #endif // DROPLETS_GRAPHICS_GRAPH_H 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Droplets 2 | 3 | Custom firmware for the Electrosmith Daisy Patch. 4 | 5 | ## Setup & Build 6 | 1. Create git repository 7 | ```sh 8 | git clone --recurse-submodules git@github.com:AquaMorph/Droplets.git 9 | ``` 10 | 2. Build project 11 | ```sh 12 | make lib && make 13 | ``` 14 | 15 | ## Deploy 16 | 1. Plug usb into Daisy. Enter bootloader mode by holding the BOOT button down, and then pressing the RESET button. Once you release the RESET button, you can also let go of the BOOT button 17 | 2. Write the binary to the Daisy 18 | ```sh 19 | make deploy 20 | ``` 21 | 22 | ## Creating Droplets 23 | 24 | The Droplets firmware is based around writing modular code blocks called droplets. A droplet needs to support three states (full, left, right). Droplets should have an animated title bar that ideally interacts with the current state of the droplet. 25 | 26 | ### Hardware Allocation 27 | 28 | #### Full Mode 29 | 30 | - CTRL [1-4] 31 | - GATE IN [1-2] 32 | - GATE OUT 1 33 | - AUDIO IN [1-4] 34 | - AUDIO OUT [1-4] 35 | - MIDI IN 36 | - MIDI OUT 37 | - CV OUT [1-2] 38 | 39 | #### Left Mode 40 | 41 | - CTRL [1-2] 42 | - GATE IN 1 43 | - GATE OUT 1 44 | - AUDIO IN [1-2] 45 | - AUDIO OUT [1-2] 46 | - MIDI IN 47 | - MIDI OUT 48 | - CV OUT 1 49 | 50 | #### Right Mode 51 | 52 | - CTRL [3-4] 53 | - GATE IN 2 54 | - AUDIO IN [3-4] 55 | - AUDIO OUT [3-4] 56 | - CV OUT 2 57 | 58 | ### Setting Up Code 59 | 60 | 1. Create new c++ program and header file from the Droplet template. 61 | 62 | ```sh 63 | create-new-droplet.sh [Droplet Name] 64 | ``` 65 | 66 | 2. Add a MenuState enum for the new droplet in the `menu_item.h` with the format of k[Droplet Name]. 67 | 68 | 3. Add menu to the `menu.cpp` by creating a new AddItemEnd to the list with the MenuState and droplet name. 69 | 70 | 4. Include the droplet header in the `main.h` file. 71 | 72 | 5. In the GetDroplet function in `main.cpp` add a case for the new droplet to return a constructor. 73 | -------------------------------------------------------------------------------- /src/droplets/droplet.cpp: -------------------------------------------------------------------------------- 1 | #include "droplet.h" 2 | 3 | Droplet::Droplet(DaisyPatch* m_patch, DropletState m_state) { 4 | patch = m_patch; 5 | UpdateState(m_state); 6 | } 7 | 8 | DaisyPatch* Droplet::Patch() { 9 | return patch; 10 | } 11 | 12 | DropletState* Droplet::State() { 13 | return &state; 14 | } 15 | 16 | DropletState Droplet::GetState() { 17 | return state; 18 | } 19 | 20 | bool Droplet::IsLeft() { 21 | return GetState() == DropletState::kLeft; 22 | } 23 | 24 | bool Droplet::IsRight() { 25 | return GetState() == DropletState::kRight; 26 | } 27 | 28 | bool Droplet::IsFull() { 29 | return GetState() == DropletState::kFull; 30 | } 31 | 32 | 33 | int Droplet::GetTitleHeight() { 34 | return kTitleHeight; 35 | } 36 | 37 | int Droplet::GetScreenWidth() { 38 | return screen_max - screen_min; 39 | } 40 | 41 | int Droplet::GetScreenMin() { 42 | return screen_min; 43 | } 44 | 45 | int Droplet::GetScreenMax() { 46 | return screen_max; 47 | } 48 | 49 | size_t Droplet::GetChannelMin() { 50 | return chn_min; 51 | } 52 | 53 | size_t Droplet::GetChannelMax() { 54 | return chn_max; 55 | } 56 | 57 | void Droplet::DrawName(std::string name) { 58 | WriteCenteredString(patch, (screen_min + screen_max) / 2, 0, 59 | Font_6x8, name); 60 | } 61 | 62 | void Droplet::UpdateState(DropletState m_state) { 63 | state = m_state; 64 | chn_min = 0; 65 | chn_max = 4; 66 | screen_min = 0; 67 | screen_max = SSD1309_WIDTH; 68 | if (state == DropletState::kLeft) { 69 | chn_max = 2; 70 | screen_max = SSD1309_WIDTH / 2; 71 | } else if (state == DropletState::kRight) { 72 | chn_min = 2; 73 | screen_min = SSD1309_WIDTH / 2; 74 | } 75 | SetControls(); 76 | UpdateStateCallback(); 77 | } 78 | 79 | void Droplet::AnimationInc() { 80 | if (count == animation_rate) { 81 | animation_count++; 82 | count = 0; 83 | } 84 | count++; 85 | } 86 | 87 | void Droplet::SetAnimationRate(int rate) { 88 | animation_rate = rate; 89 | } 90 | 91 | int Droplet::GetAnimationCount() { 92 | return animation_count; 93 | } 94 | 95 | bool Droplet::NeedUpdate() { 96 | return count % animation_rate == 0; 97 | } 98 | -------------------------------------------------------------------------------- /src/droplets/vco_droplet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_VCO_DROPLET_H_ 4 | #define DROPLETS_VCO_DROPLET_H_ 5 | 6 | #include 7 | 8 | #include "daisysp.h" 9 | #include "daisy_patch.h" 10 | 11 | #include "droplet.h" 12 | #include "../util.h" 13 | #include "../graphics/wave.h" 14 | 15 | using namespace daisy; 16 | using namespace daisysp; 17 | 18 | class VCODroplet: public Droplet { 19 | private: 20 | Oscillator osc; 21 | Parameter freqctrl, wavectrl, ampctrl, finectrl; 22 | size_t wave; 23 | size_t last_wave_ctrl; 24 | const double pi = std::acos(-1); 25 | Wave* wave_graphic = new Wave(WaveShape::kTriangle, 21, GetTitleHeight()); 26 | 27 | /* 28 | * Sets the vco wave shap to display on screen. 29 | * 30 | * @param wf wave shape 31 | */ 32 | void SetWaveState(uint8_t wf); 33 | public: 34 | /* 35 | * Constructor for voltage control oscillator droplet. 36 | * 37 | * @param m_patch pointer to patch 38 | * @param m_state droplet position 39 | * @param sample_rate audio sample rate 40 | */ 41 | VCODroplet(DaisyPatch* m_patch, 42 | DropletState m_state, 43 | float sample_rate); 44 | 45 | /* 46 | * Destructor for vco droplet. 47 | */ 48 | ~VCODroplet(); 49 | 50 | /* 51 | * Processes user controls and inputs. 52 | */ 53 | void Control(); 54 | 55 | /* 56 | * Processes audio input and outputs. 57 | * 58 | * @param in the audio inputs for the patch 59 | * @param out the audio outputs for the patch 60 | * @param size the number of inputs and outputs 61 | */ 62 | void Process(AudioHandle::InputBuffer in, 63 | AudioHandle::OutputBuffer out, 64 | size_t size); 65 | 66 | /* 67 | * Processes information to be shown on the display. 68 | */ 69 | void Draw(); 70 | 71 | 72 | /* 73 | * Runs when droplet state is updated. 74 | */ 75 | void UpdateStateCallback(); 76 | 77 | /* 78 | * Changes the wave shape of the VCO. 79 | * 80 | * @param amount wave shape table position adjustment 81 | */ 82 | void AdjustWaveShape(int amount); 83 | 84 | /* 85 | * Sets the wave shape of the VCO. 86 | * 87 | * @param ws wave shape 88 | */ 89 | void SetWaveShape(int ws); 90 | 91 | /* 92 | * Set up the controls for the droplet. 93 | */ 94 | void SetControls(); 95 | }; 96 | 97 | #endif // DROPLETS_VCO_DROPLET_H_ 98 | -------------------------------------------------------------------------------- /src/droplets/ladder_filter_droplet.cpp: -------------------------------------------------------------------------------- 1 | #include "ladder_filter_droplet.h" 2 | 3 | LadderFilterDroplet::LadderFilterDroplet(DaisyPatch* m_patch, 4 | DropletState m_state, 5 | float sample_rate) : 6 | Droplet(m_patch, 7 | m_state) { 8 | 9 | for (int i = 0; i < 4; i++) { 10 | filter[i].Init(sample_rate); 11 | } 12 | 13 | SetControls(); 14 | } 15 | 16 | LadderFilterDroplet::~LadderFilterDroplet() {} 17 | 18 | void LadderFilterDroplet::Control() {} 19 | 20 | void LadderFilterDroplet::Process(AudioHandle::InputBuffer in, 21 | AudioHandle::OutputBuffer out, 22 | size_t size) { 23 | freq = freq_ctrl.Process(); 24 | res = res_ctrl.Process(); 25 | 26 | for (size_t i = 0; i < size; i++) { 27 | for (size_t chn = GetChannelMin(); chn < GetChannelMax(); chn++) { 28 | filter[chn].SetFreq(freq); 29 | filter[chn].SetRes(res); 30 | out[chn][i] = filter[chn].Process(in[chn][i]) * (1.0f+res*4); 31 | } 32 | } 33 | } 34 | 35 | void LadderFilterDroplet::Draw() { 36 | int filter_cutoff_line = GetScreenMin()+(GetScreenWidth()*(freq/FILTER_MAX)); 37 | DrawSolidRect(Patch(), 38 | GetScreenMin(), 39 | 3, 40 | filter_cutoff_line, 41 | GetTitleHeight()-1, 42 | true); 43 | 44 | // Draw filter roll off 45 | Patch()->display.DrawLine(filter_cutoff_line+1, 5, 46 | filter_cutoff_line+1, GetTitleHeight()-1, true); 47 | 48 | // Draw Res 49 | res_points[0] = filter_cutoff_line-7*(res/RES_MAX); 50 | res_points[1] = filter_cutoff_line-3; 51 | res_points[2] = filter_cutoff_line-11*(res/RES_MAX); 52 | res_points[3] = filter_cutoff_line-2; 53 | res_points[4] = filter_cutoff_line-14*(res/RES_MAX); 54 | res_points[5] = filter_cutoff_line-1; 55 | 56 | for (int i = 0; i < 6; i++) { 57 | res_points[i] = std::max(GetScreenMin(), res_points[i]); 58 | } 59 | 60 | if (res > 0.6*RES_MAX) { 61 | Patch()->display.DrawLine(res_points[0], 0, res_points[1], 0, true); 62 | } 63 | if (res > 0.3*RES_MAX) { 64 | Patch()->display.DrawLine(res_points[2], 1, res_points[3], 1, true); 65 | } 66 | if (res > 0.1*RES_MAX) { 67 | Patch()->display.DrawLine(res_points[4], 2, res_points[5], 2, true); 68 | } 69 | 70 | DrawName("Ladder"); 71 | } 72 | 73 | void LadderFilterDroplet::UpdateStateCallback() {} 74 | 75 | void LadderFilterDroplet::SetControls() { 76 | AnalogControl filter_knob, res_knob; 77 | if (IsRight()) { 78 | filter_knob = Patch()->controls[Patch()->CTRL_3]; 79 | res_knob = Patch()->controls[Patch()->CTRL_4]; 80 | } else { 81 | filter_knob = Patch()->controls[Patch()->CTRL_1]; 82 | res_knob = Patch()->controls[Patch()->CTRL_2]; 83 | } 84 | freq_ctrl.Init(filter_knob, 5.0f, FILTER_MAX, Parameter::LOGARITHMIC); 85 | res_ctrl.Init(res_knob, 0.0f, RES_MAX, Parameter::LINEAR); 86 | } 87 | -------------------------------------------------------------------------------- /src/menu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_MENU_H_ 4 | #define DROPLETS_MENU_H_ 5 | 6 | #include "daisy_patch.h" 7 | #include "menu_item.h" 8 | #include "util.h" 9 | #include "droplets/droplet_manager.h" 10 | 11 | #include 12 | 13 | using namespace daisy; 14 | 15 | const int MAX_CHAR_LENGTH = 15; 16 | const int MENU_X[] = {0, 5, 10, 5, 0}; 17 | const int MENU_Y[] = {0, 11, 22, 41, 52}; 18 | 19 | class Menu { 20 | private: 21 | DaisyPatch* patch; 22 | DropletManager* manager; 23 | DropletState state; 24 | MenuItem* selected; 25 | MenuItem* buffer; 26 | MenuItem* highlighted; 27 | MenuItem* head; 28 | bool inMenu = false; 29 | 30 | /* 31 | * Draws a menu item on screen. 32 | * 33 | * @param text menu item name 34 | * @param position menu items position in the menu 35 | * @param highlighted state of menu items selection 36 | */ 37 | void CreateMenuItem(std::string text, 38 | int position, 39 | bool highlighted); 40 | public: 41 | /* 42 | * Constructor for a menu system to control the state of the patch. 43 | * 44 | * @param m_patch pointer to patch 45 | * @param m_name name of the menu 46 | * @param m_split one or two droplets 47 | */ 48 | Menu(DaisyPatch* m_patch, DropletState m_state, DropletManager* m_manager); 49 | 50 | /* 51 | * Gives if the user is currently in the menu. 52 | * 53 | * @return menu active 54 | */ 55 | bool InMenu(); 56 | 57 | /* 58 | * Sets if the user is in the menu. 59 | * 60 | * @param menu active 61 | */ 62 | void SetInMenu(bool); 63 | 64 | /* 65 | * Draws droplet information on the screen. 66 | */ 67 | void ProcessMenuOled(); 68 | 69 | /* 70 | * Updates menu position based on user input from the encoder. 71 | */ 72 | void UpdateMenuPosition(); 73 | 74 | /* 75 | * Returns the name of the currently selected menu item. 76 | * 77 | * @return selected menu item's name 78 | */ 79 | std::string SelectedName(); 80 | 81 | /* 82 | * Returns the currently selected menu item. 83 | * 84 | * @return menu state 85 | */ 86 | MenuState GetState(); 87 | 88 | /* 89 | * Set the menu state. 90 | * 91 | * @param state menu state 92 | */ 93 | void SetState(MenuState state); 94 | 95 | /* 96 | * Returns the previously selected menu item. 97 | * 98 | * @return menu state 99 | */ 100 | MenuState GetBufferState(); 101 | 102 | /* 103 | * Updates the menu upon a split or a merge. 104 | */ 105 | void UpdateMenuState(); 106 | 107 | 108 | /* 109 | * Select the currently highlighted menu item. 110 | */ 111 | void Select(); 112 | 113 | /* 114 | * Select the given menu item. 115 | * 116 | * @param item menu item to select 117 | */ 118 | void Select(MenuItem* item); 119 | }; 120 | 121 | #endif // DROPLETS_MENU_H_ 122 | -------------------------------------------------------------------------------- /src/menu_item.cpp: -------------------------------------------------------------------------------- 1 | #include "menu_item.h" 2 | 3 | MenuItem::MenuItem(MenuState m_state, std::string m_title) { 4 | state = m_state; 5 | this->SetTitle(m_title); 6 | visible = true; 7 | previous = NULL; 8 | next = NULL; 9 | } 10 | 11 | std::string MenuItem::GetTitle() { 12 | return title; 13 | } 14 | 15 | void MenuItem::SetTitle(std::string m_title) { 16 | title = m_title; 17 | } 18 | 19 | MenuItem* MenuItem::GetPrevious() { 20 | return previous; 21 | } 22 | 23 | void MenuItem::SetPrevious(MenuItem* item) { 24 | previous = item; 25 | } 26 | 27 | MenuItem* MenuItem::GetNext() { 28 | return next; 29 | } 30 | 31 | void MenuItem::SetNext(MenuItem* item) { 32 | next = item; 33 | } 34 | 35 | MenuItem* MenuItem::GetPreviousVisible() { 36 | if (this->GetPrevious() == NULL || 37 | this->GetPrevious()->IsVisible()) { 38 | return this->GetPrevious(); 39 | } else { 40 | return this->GetPrevious()->GetPreviousVisible(); 41 | } 42 | } 43 | 44 | MenuItem* MenuItem::GetNextVisible() { 45 | if (this->GetNext() == NULL || 46 | this->GetNext()->IsVisible()) { 47 | return this->GetNext(); 48 | } else { 49 | return this->GetNext()->GetNextVisible(); 50 | } 51 | } 52 | 53 | bool MenuItem::IsVisible() { 54 | return visible; 55 | } 56 | 57 | void MenuItem::SetVisibility(bool m_visible) { 58 | visible = m_visible; 59 | } 60 | 61 | void MenuItem::ToggleVisibility() { 62 | visible = !visible; 63 | } 64 | 65 | void MenuItem::SetStateVisibility(MenuState m_state, bool visibility) { 66 | if (this->GetState() == m_state) { 67 | this->SetVisibility(visibility); 68 | } 69 | if (this->GetNext() != NULL) { 70 | this->GetNext()->SetStateVisibility(m_state, visibility); 71 | } 72 | } 73 | 74 | void MenuItem::SetStateTitle(MenuState m_state, std::string m_title) { 75 | if (this->GetState() == m_state) { 76 | this->SetTitle(m_title); 77 | } 78 | if (this->GetNext() != NULL) { 79 | this->GetNext()->SetStateTitle(m_state, m_title); 80 | } 81 | } 82 | 83 | MenuState MenuItem::GetState() { 84 | return state; 85 | } 86 | 87 | void MenuItem::AddItemBefore(MenuItem* item) { 88 | item->SetNext(this); 89 | item->SetPrevious(this->GetPrevious()); 90 | this->SetPrevious(item); 91 | } 92 | 93 | void MenuItem::AddItemAfter(MenuItem* item) { 94 | item->SetPrevious(this); 95 | item->SetNext(this->GetNext()); 96 | this->SetNext(item); 97 | } 98 | 99 | void MenuItem::AddItemStart(MenuItem* item) { 100 | if (this->GetPrevious() == NULL) { 101 | item->SetNext(this); 102 | this->SetPrevious(item); 103 | } else { 104 | this->GetPrevious()->AddItemStart(item); 105 | } 106 | } 107 | 108 | void MenuItem::AddItemEnd(MenuItem* item) { 109 | if (this->GetNext() == NULL) { 110 | item->SetPrevious(this); 111 | this->SetNext(item); 112 | } else { 113 | this->GetNext()->AddItemEnd(item); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/droplets/sequencer_droplet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_SEQUENCER_DROPLET_H_ 4 | #define DROPLETS_SEQUENCER_DROPLET_H_ 5 | 6 | #include "daisysp.h" 7 | #include "daisy_patch.h" 8 | 9 | #include "droplet.h" 10 | #include "../util.h" 11 | #include "../graphics/graph.h" 12 | 13 | #define MAX_SEQUENCE_LENGTH 32 14 | #define MAX_CLOCK_DIVIDE 16 15 | #define CONTROL_DEADZONE 0.00499f 16 | #define CONTROL_RATE_LIMIT 25 17 | #define NUM_ROWS 6 18 | 19 | class SequencerDroplet: public Droplet { 20 | private: 21 | int step = 0; 22 | int selected = 0; 23 | int sequence_length = 16; 24 | int num_columns = 4; 25 | int num_rows = NUM_ROWS; 26 | int control_rate_count = 0; 27 | int divider = 0; 28 | int divider_count = 1; 29 | float sequence[MAX_SEQUENCE_LENGTH] = { 0.0f }; 30 | Parameter control[4]; 31 | float last_control_value[4] = { 0.0f }; 32 | std::string length_text = ""; 33 | Graph* title_graph; 34 | 35 | /* 36 | * Set the sequencer to the next step. 37 | */ 38 | void Step(); 39 | 40 | /* 41 | * Reset the sequencers to the first step of the sequence. 42 | */ 43 | void Reset(); 44 | 45 | /* 46 | * Set the screeen dimensions of the sequence. 47 | */ 48 | void SetDimensions(); 49 | 50 | /* 51 | * Change the selected menu input. 52 | * 53 | * @param adj amount to adjust menu 54 | */ 55 | void AdjustSelected(int adj); 56 | 57 | /* 58 | * Returns if the sequencer menu is selected. 59 | * 60 | * @return menu state 61 | */ 62 | bool InMenu(); 63 | 64 | /* 65 | * Set sequancer in the menu. 66 | */ 67 | void SetInMenu(); 68 | 69 | /* 70 | * Create a new graph for the title bar. 71 | */ 72 | void CreateTitleGraph(); 73 | public: 74 | /* 75 | * Constructor for a droplet. 76 | * 77 | * @param m_patch pointer to patch 78 | * @param m_state droplet position 79 | * @param sample_rate audio sample rate 80 | */ 81 | SequencerDroplet(DaisyPatch* m_patch, 82 | DropletState m_state, 83 | float sample_rate); 84 | 85 | /* 86 | * Destructor for sequencer droplet. 87 | */ 88 | ~SequencerDroplet(); 89 | 90 | /* 91 | * Processes user controls and inputs. 92 | */ 93 | void Control(); 94 | 95 | /* 96 | * Processes audio input and outputs. 97 | * 98 | * @param in the audio inputs for the patch 99 | * @param out the audio outputs for the patch 100 | * @param size the number of inputs and outputs 101 | */ 102 | void Process(AudioHandle::InputBuffer in, 103 | AudioHandle::OutputBuffer out, 104 | size_t size); 105 | 106 | /* 107 | * Processes information to be shown on the display. 108 | */ 109 | void Draw(); 110 | 111 | /* 112 | * Runs when droplet state is updated. 113 | */ 114 | void UpdateStateCallback(); 115 | 116 | /* 117 | * Set up the controls for the droplet. 118 | */ 119 | void SetControls(); 120 | }; 121 | 122 | #endif // DROPLETS_SEQUENCER_DROPLET_H_ 123 | -------------------------------------------------------------------------------- /src/graphics/sprite.cpp: -------------------------------------------------------------------------------- 1 | #include "sprite.h" 2 | 3 | Sprite::Sprite(int m_width, int m_height) { 4 | width = m_width; 5 | height = m_height; 6 | sprite = new bool*[width]; 7 | for (int w = 0; w < width; w++) { 8 | sprite[w] = new bool[height]; 9 | for (int h = 0; h < height; h++) { 10 | sprite[w][h] = false; 11 | } 12 | } 13 | } 14 | 15 | Sprite::~Sprite() { 16 | for (int w = 0; w < width; w++) { 17 | delete[] sprite[w]; 18 | } 19 | delete[] sprite; 20 | } 21 | 22 | void Sprite::AddPixel(int x, int y, bool on) { 23 | sprite[x][height-y-1] = on; 24 | } 25 | 26 | void Sprite::AddLine(int x1, 27 | int y1, 28 | int x2, 29 | int y2, 30 | bool on) { 31 | uint8_t deltaX = abs(x2 - x1); 32 | uint8_t deltaY = abs(y2 - y1); 33 | int8_t signX = ((x1 < x2) ? 1 : -1); 34 | int8_t signY = ((y1 < y2) ? 1 : -1); 35 | int16_t error = deltaX - deltaY; 36 | int16_t error2; 37 | 38 | AddPixel(x2, y2, on); 39 | while((x1 != x2) || (y1 != y2)) { 40 | AddPixel(x1, y1, on); 41 | error2 = error * 2; 42 | if(error2 > -deltaY) { 43 | error -= deltaY; 44 | x1 += signX; 45 | } 46 | if(error2 < deltaX) { 47 | error += deltaX; 48 | y1 += signY; 49 | } 50 | } 51 | } 52 | 53 | int Sprite::GetHeight() { 54 | return height; 55 | } 56 | 57 | int Sprite::GetWidth() { 58 | return width; 59 | 60 | } 61 | 62 | void Sprite::Draw(DaisyPatch* patch, int x, int y) { 63 | for (int w = 0; w < width; w++) { 64 | for (int h = 0; h < height; h++) { 65 | patch->display.DrawPixel(x+w, y+h, 66 | sprite[GetShiftArrayX(w)] 67 | [GetShiftArrayY(height-h)]); 68 | } 69 | } 70 | } 71 | 72 | void Sprite::DrawTile(DaisyPatch* patch, 73 | int x1, 74 | int y1, 75 | int x2, 76 | int y2) { 77 | int x_min = std::min(x1, x2); 78 | int x_max = std::max(x1, x2); 79 | int y_min = std::min(y1, y2); 80 | int y_max = std::max(y1, y2); 81 | int x, y; 82 | for (int w = x_min; w < x_max; w++) { 83 | for (int h = y_min; h < y_max; h++) { 84 | x = GetShiftArrayX((w-x_min) % width); 85 | y = GetShiftArrayY((h-y_min) % height); 86 | patch->display.DrawPixel(w, h, sprite[x][y]); 87 | } 88 | } 89 | } 90 | 91 | bool** Sprite::GetSprite() { 92 | return sprite; 93 | } 94 | 95 | void Sprite::SetBlank() { 96 | for (int w = 0; w < width; w++) { 97 | for (int h = 0; h < height; h++) { 98 | sprite[w][h] = false; 99 | } 100 | } 101 | } 102 | 103 | void Sprite::SetXShift(int x) { 104 | x_shift = x; 105 | } 106 | 107 | void Sprite::SetYShift(int y) { 108 | y_shift = y; 109 | } 110 | 111 | void Sprite::AdjustXShift(int x) { 112 | x_shift -= x; 113 | } 114 | 115 | void Sprite::AdjustYShift(int y) { 116 | y_shift += y; 117 | } 118 | 119 | int Sprite::GetShiftArrayX(int pos) { 120 | return GetShiftArray(pos, x_shift, width); 121 | } 122 | 123 | int Sprite::GetShiftArrayY(int pos) { 124 | return GetShiftArray(pos, y_shift, height); 125 | } 126 | -------------------------------------------------------------------------------- /src/droplets/vca_droplet.cpp: -------------------------------------------------------------------------------- 1 | #include "vca_droplet.h" 2 | 3 | VCADroplet::VCADroplet(DaisyPatch* m_patch, 4 | DropletState m_state) : 5 | Droplet(m_patch, 6 | m_state) { 7 | SetControls(); 8 | } 9 | 10 | VCADroplet::~VCADroplet() {} 11 | 12 | void VCADroplet::Control() {} 13 | 14 | void VCADroplet::Process(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { 15 | Patch()->ProcessAnalogControls(); 16 | 17 | for (size_t i = 0; i < size; i++) { 18 | for (size_t chn = GetChannelMin(); chn < GetChannelMax(); chn++) { 19 | out[chn][i] = in[chn][i] * vca[chn].Process(); 20 | } 21 | } 22 | } 23 | 24 | void VCADroplet::Draw() { 25 | int divider; 26 | switch (GetState()) { 27 | default: 28 | case DropletState::kFull: 29 | divider = (GetScreenMax()-GetScreenMin())/5; 30 | DrawFourDividedRectangles(Patch(), 31 | divider, 32 | GetScreenMin(), 33 | GetScreenMax(), 34 | 0, 35 | GetTitleHeight()*vca[0].Process(), 36 | 0, 37 | GetTitleHeight()*vca[1].Process(), 38 | 0, 39 | GetTitleHeight()*vca[2].Process(), 40 | 0, 41 | GetTitleHeight()*vca[3].Process()); 42 | break; 43 | case DropletState::kLeft: 44 | divider = (GetScreenMax()-GetScreenMin())/3; 45 | DrawTwoDividedRectangles(Patch(), 46 | divider, 47 | GetScreenMin(), 48 | GetScreenMax(), 49 | 0, 50 | GetTitleHeight()*vca[0].Process(), 51 | 0, 52 | GetTitleHeight()*vca[1].Process()); 53 | break; 54 | case DropletState::kRight: 55 | divider = (GetScreenMax()-GetScreenMin())/3; 56 | DrawTwoDividedRectangles(Patch(), 57 | divider, 58 | GetScreenMin(), 59 | GetScreenMax(), 60 | 0, 61 | GetTitleHeight()*vca[2].Process(), 62 | 0, 63 | GetTitleHeight()*vca[3].Process()); 64 | break; 65 | } 66 | DrawName("VCA"); 67 | } 68 | 69 | void VCADroplet::UpdateStateCallback() { 70 | SetControls(); 71 | } 72 | 73 | void VCADroplet::SetControls() { 74 | switch (GetState()) { 75 | default: 76 | case DropletState::kFull: 77 | vca[0].Init(Patch()->controls[Patch()->CTRL_1], 78 | 0.0, 1.0f, Parameter::LINEAR); 79 | vca[1].Init(Patch()->controls[Patch()->CTRL_2], 80 | 0.0, 1.0f, Parameter::LINEAR); 81 | vca[2].Init(Patch()->controls[Patch()->CTRL_3], 82 | 0.0, 1.0f, Parameter::LINEAR); 83 | vca[3].Init(Patch()->controls[Patch()->CTRL_4], 84 | 0.0, 1.0f, Parameter::LINEAR); 85 | break; 86 | case DropletState::kLeft: 87 | vca[0].Init(Patch()->controls[Patch()->CTRL_1], 88 | 0.0, 1.0f, Parameter::LINEAR); 89 | vca[1].Init(Patch()->controls[Patch()->CTRL_2], 90 | 0.0, 1.0f, Parameter::LINEAR); 91 | break; 92 | case DropletState::kRight: 93 | vca[2].Init(Patch()->controls[Patch()->CTRL_3], 94 | 0.0, 1.0f, Parameter::LINEAR); 95 | vca[3].Init(Patch()->controls[Patch()->CTRL_4], 96 | 0.0, 1.0f, Parameter::LINEAR); 97 | break; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/droplets/lfo_droplet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_LFO_DROPLET_H_ 4 | #define DROPLETS_LFO_DROPLET_H_ 5 | 6 | #include "daisysp.h" 7 | #include "daisy_patch.h" 8 | 9 | #include "droplet.h" 10 | #include "../util.h" 11 | #include "../graphics/graph.h" 12 | 13 | using namespace daisy; 14 | using namespace daisysp; 15 | 16 | #define MAX_WAVE Oscillator::WAVE_POLYBLEP_TRI 17 | 18 | class LFO { 19 | private: 20 | Oscillator osc; 21 | Parameter freqCtrl; 22 | Parameter ampCtrl; 23 | float amp; 24 | float freq; 25 | uint8_t wave; 26 | float value; 27 | DaisyPatch* patch; 28 | public: 29 | /* 30 | * Constructor for a LFO. 31 | * 32 | * @param m_patch pointer to patch 33 | * @param sample_rate audio sample rate 34 | * @param freqKnob frequence knob 35 | * @param ampKnob amp knob 36 | */ 37 | void Init(DaisyPatch* m_patch, 38 | float sample_rate, 39 | AnalogControl freqKnob, 40 | AnalogControl ampKnob); 41 | 42 | /* 43 | * Send LFO signal to the given channel. 44 | * 45 | * @param chn output channel 46 | */ 47 | void Process(DacHandle::Channel chn); 48 | 49 | /* 50 | * Shift the wavestate. 51 | * 52 | * @param change amount to shift the waveshape 53 | */ 54 | void UpdateWave(int change); 55 | 56 | /* 57 | * Returns the current waveshape of the LFO. 58 | * 59 | * @return lfo waveshape 60 | */ 61 | uint8_t GetWave(); 62 | 63 | /* 64 | * Returns LFO signal from 0,0 to 1.0. 65 | * 66 | * @return LFO signal 67 | */ 68 | float GetSignal(); 69 | }; 70 | 71 | class LFODroplet: public Droplet { 72 | private: 73 | LFO lfo[2]; 74 | Graph* title_graph; 75 | float sample_rate; 76 | 77 | /* 78 | * Create a new graph for the title bar. 79 | */ 80 | void CreateTitleGraph(); 81 | 82 | public: 83 | /* 84 | * Constructor for a LFO droplet. 85 | * 86 | * @param m_patch pointer to patch 87 | * @param m_state droplet position 88 | * @param sample_rate audio sample rate 89 | */ 90 | LFODroplet(DaisyPatch* m_patch, 91 | DropletState m_state, 92 | float sample_rate); 93 | 94 | /* 95 | * Destructor for vco droplet. 96 | */ 97 | ~LFODroplet(); 98 | 99 | /* 100 | * Processes user controls and inputs. 101 | */ 102 | void Control(); 103 | 104 | /* 105 | * Processes audio input and outputs. 106 | * 107 | * @param in the audio inputs for the patch 108 | * @param out the audio outputs for the patch 109 | * @param size the number of inputs and outputs 110 | */ 111 | void Process(AudioHandle::InputBuffer in, 112 | AudioHandle::OutputBuffer out, 113 | size_t size); 114 | 115 | /* 116 | * Processes information to be shown on the display. 117 | */ 118 | void Draw(); 119 | 120 | /* 121 | * Runs when droplet state is updated. 122 | */ 123 | void UpdateStateCallback(); 124 | 125 | /* 126 | * Set up the controls for the droplet. 127 | */ 128 | void SetControls(); 129 | }; 130 | 131 | #endif // DROPLETS_LFO_DROPLET_H_ 132 | -------------------------------------------------------------------------------- /src/menu_item.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_MENU_ITEM_H_ 4 | #define DROPLETS_MENU_ITEM_H_ 5 | 6 | #include 7 | 8 | enum class MenuState {kSplit, kChange, kAD, kLadderFilter, 9 | kLFO, kMixer, kNoise, kSequencer, kVCA, kVCO}; 10 | 11 | class MenuItem { 12 | private: 13 | MenuState state; 14 | std::string title; 15 | bool visible; 16 | MenuItem* previous; 17 | MenuItem* next; 18 | public: 19 | MenuItem(MenuState m_state, std::string m_title); 20 | 21 | /* 22 | * Returns the title of the menu item. 23 | * 24 | * @return menu item title 25 | */ 26 | std::string GetTitle(); 27 | 28 | /* 29 | * Sets the title for a munu item. 30 | * 31 | * @param menu title 32 | */ 33 | void SetTitle(std::string m_title); 34 | 35 | /* 36 | * Returns the previous menu item. 37 | * 38 | * @return prvious menu item 39 | */ 40 | MenuItem* GetPrevious(); 41 | 42 | /* 43 | * Sets the previous menu item. 44 | * 45 | * @param previous menu item 46 | */ 47 | void SetPrevious(MenuItem* item); 48 | 49 | /* 50 | * Returns the next menu item. 51 | * 52 | * @return next menu item 53 | */ 54 | MenuItem* GetNext(); 55 | /* 56 | * Sets the next menu item, 57 | * 58 | * @param next menu item 59 | */ 60 | void SetNext(MenuItem* item); 61 | 62 | /* 63 | * Returns the previous visible menu item. 64 | * 65 | * @return previous visible munu item 66 | */ 67 | MenuItem* GetPreviousVisible(); 68 | 69 | /* 70 | * Returns the next visible menu item. 71 | * 72 | * @return next visible menu item 73 | */ 74 | MenuItem* GetNextVisible(); 75 | 76 | /* 77 | * Returns if the menu item is visible. 78 | * 79 | * @return visibility 80 | */ 81 | bool IsVisible(); 82 | 83 | /* 84 | * Sets the visibility of the menu item. 85 | * 86 | * @param visibility state 87 | */ 88 | void SetVisibility(bool m_visible); 89 | 90 | /* 91 | * Toggles visibility of menu item. 92 | */ 93 | void ToggleVisibility(); 94 | 95 | /* 96 | * Set the visibility of all menu items with a given state. 97 | * 98 | * @param state 99 | * @param visibility 100 | */ 101 | void SetStateVisibility(MenuState m_state, bool visibility); 102 | 103 | /* 104 | * Set the title of all menu items with a given state. 105 | * 106 | * @param state 107 | * @param title 108 | */ 109 | void SetStateTitle(MenuState m_state, std::string m_title); 110 | 111 | /* 112 | * Returns the state of the menu item. 113 | * 114 | * @return state 115 | */ 116 | MenuState GetState(); 117 | 118 | /* 119 | * Add menu item before other menu item. 120 | * 121 | * @param menu item to be added 122 | */ 123 | void AddItemBefore(MenuItem* item); 124 | 125 | /* 126 | * Add menu item after other menu item. 127 | * 128 | * @param menu item to be added 129 | */ 130 | void AddItemAfter(MenuItem* item); 131 | 132 | /* 133 | * Add menu item at the beginning of a a menu item list. 134 | * 135 | * @param menu item to be added 136 | */ 137 | void AddItemStart(MenuItem* item); 138 | 139 | /* 140 | * Add menu item at the end of a a menu item list. 141 | * 142 | * @param menu item to be added 143 | */ 144 | void AddItemEnd(MenuItem* item); 145 | }; 146 | 147 | #endif // DROPLETS_MENU_ITEM_H_ 148 | -------------------------------------------------------------------------------- /src/droplets/mixer_droplet.cpp: -------------------------------------------------------------------------------- 1 | #include "mixer_droplet.h" 2 | 3 | MixerDroplet::MixerDroplet(DaisyPatch* m_patch, 4 | DropletState m_state) : 5 | Droplet(m_patch, 6 | m_state) { 7 | SetControls(); 8 | } 9 | 10 | MixerDroplet::~MixerDroplet() {} 11 | 12 | void MixerDroplet::Control() {} 13 | 14 | void MixerDroplet::Process(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { 15 | Patch()->ProcessAnalogControls(); 16 | 17 | float output = 0.0f; 18 | for (size_t i = 0; i < size; i++) { 19 | output = 0.0f; 20 | for (size_t chn = GetChannelMin(); chn < GetChannelMax(); chn++) { 21 | output += in[chn][i] * mix[chn].Process(); 22 | } 23 | if (IsFull()) { 24 | output *= .25f; 25 | } else { 26 | output *= .5f; 27 | } 28 | for (size_t chn = GetChannelMin(); chn < GetChannelMax(); chn++) { 29 | out[chn][i] = output; 30 | } 31 | } 32 | } 33 | 34 | void MixerDroplet::Draw() { 35 | int divider; 36 | switch (GetState()) { 37 | default: 38 | case DropletState::kFull: 39 | divider = (GetScreenMax()-GetScreenMin())/5.5; 40 | DrawFourDividedRectangles(Patch(), 41 | divider, 42 | GetScreenMin(), 43 | GetScreenMax(), 44 | GetTitleHeight()*(1.0f-mix[0].Process()), 45 | GetTitleHeight()-1, 46 | GetTitleHeight()*(1.0f-mix[1].Process()), 47 | GetTitleHeight()-1, 48 | GetTitleHeight()*(1.0f-mix[2].Process()), 49 | GetTitleHeight()-1, 50 | GetTitleHeight()*(1.0f-mix[3].Process()), 51 | GetTitleHeight()-1); 52 | break; 53 | case DropletState::kLeft: 54 | divider = (GetScreenMax()-GetScreenMin())/4.5; 55 | DrawTwoDividedRectangles(Patch(), 56 | divider, 57 | GetScreenMin(), 58 | GetScreenMax(), 59 | GetTitleHeight()*(1.0f-mix[0].Process()), 60 | GetTitleHeight()-1, 61 | GetTitleHeight()*(1.0f-mix[1].Process()), 62 | GetTitleHeight()-1); 63 | break; 64 | case DropletState::kRight: 65 | divider = (GetScreenMax()-GetScreenMin())/4.5; 66 | DrawTwoDividedRectangles(Patch(), 67 | divider, 68 | GetScreenMin(), 69 | GetScreenMax(), 70 | GetTitleHeight()*(1.0f-mix[2].Process()), 71 | GetTitleHeight()-1, 72 | GetTitleHeight()*(1.0f-mix[3].Process()), 73 | GetTitleHeight()-1); 74 | break; 75 | } 76 | DrawName("Mixer"); 77 | } 78 | 79 | void MixerDroplet::UpdateStateCallback() { 80 | SetControls(); 81 | } 82 | 83 | void MixerDroplet::SetControls() { 84 | switch (GetState()) { 85 | default: 86 | case DropletState::kFull: 87 | mix[0].Init(Patch()->controls[Patch()->CTRL_1], 88 | 0.0, 1.0f, Parameter::LINEAR); 89 | mix[1].Init(Patch()->controls[Patch()->CTRL_2], 90 | 0.0, 1.0f, Parameter::LINEAR); 91 | mix[2].Init(Patch()->controls[Patch()->CTRL_3], 92 | 0.0, 1.0f, Parameter::LINEAR); 93 | mix[3].Init(Patch()->controls[Patch()->CTRL_4], 94 | 0.0, 1.0f, Parameter::LINEAR); 95 | break; 96 | case DropletState::kLeft: 97 | mix[0].Init(Patch()->controls[Patch()->CTRL_1], 98 | 0.0, 1.0f, Parameter::LINEAR); 99 | mix[1].Init(Patch()->controls[Patch()->CTRL_2], 100 | 0.0, 1.0f, Parameter::LINEAR); 101 | break; 102 | case DropletState::kRight: 103 | mix[2].Init(Patch()->controls[Patch()->CTRL_3], 104 | 0.0, 1.0f, Parameter::LINEAR); 105 | mix[3].Init(Patch()->controls[Patch()->CTRL_4], 106 | 0.0, 1.0f, Parameter::LINEAR); 107 | break; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/graphics/sprite.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_GRAPHICS_SPRITE_H_ 4 | #define DROPLETS_GRAPHICS_SPRITE_H_ 5 | 6 | #include "daisy_patch.h" 7 | #include "../util.h" 8 | 9 | using namespace daisy; 10 | 11 | class Sprite { 12 | private: 13 | int width, height; 14 | bool** sprite; 15 | int x_shift = 0; 16 | int y_shift = 0; 17 | 18 | /* 19 | * Converts a x axis position based upon the sprites shift. 20 | * 21 | * @param pos x axis position 22 | * @return new x axis position 23 | */ 24 | int GetShiftArrayX(int pos); 25 | 26 | /* 27 | * Converts a y axis position based upon the sprites shift. 28 | * 29 | * @param pos y axis position 30 | * @return new y axis position 31 | */ 32 | int GetShiftArrayY(int pos); 33 | public: 34 | /* 35 | * Contsturctor for sprite. 36 | * 37 | * @param m_width width 38 | * @param m_height height 39 | */ 40 | Sprite(int m_width, 41 | int m_height); 42 | 43 | /* 44 | * Destructor for sprite. 45 | */ 46 | ~Sprite(); 47 | 48 | /* 49 | * Sets a pixel on the sprite. 50 | * 51 | * @param x pixel x coordinate 52 | * @param y pixel y coordinate 53 | * @param on pixel on or off 54 | */ 55 | void AddPixel(int x, 56 | int y, 57 | bool on); 58 | 59 | /* 60 | * Adds a line to the sprite. 61 | * 62 | * @param x1 x coordinate of the first point 63 | * @param y1 y coordinate of the first point 64 | * @param x2 x coordinate of the second point 65 | * @param y2 y coordinate of the second point 66 | * @param on line display on or off 67 | */ 68 | void AddLine(int x1, 69 | int y1, 70 | int x2, 71 | int y2, 72 | bool on); 73 | 74 | /* 75 | * Returns the height of the sprite. 76 | * 77 | * @return sprite height 78 | */ 79 | int GetHeight(); 80 | 81 | /* 82 | * Returns the width of the sprite. 83 | * 84 | * @return sprite width 85 | */ 86 | int GetWidth(); 87 | 88 | /* 89 | * Returns 2d array of booleans representing pixel state of sprite. 90 | * 91 | * @return sprite graphic 92 | */ 93 | bool** GetSprite(); 94 | 95 | /* 96 | * Draws sprite on display. 97 | * 98 | * @param patch daisy patch board 99 | * @param x starting x coordinate of sprite 100 | * @param y starting y coordinate of sprite 101 | */ 102 | void Draw(DaisyPatch* patch, 103 | int x, 104 | int y); 105 | 106 | /* 107 | * Draws the sprite on the display where the sprite will form 108 | * a tile pattern when drawn on a canvus larger than the sprite. 109 | * 110 | * @param patch daisy patch board 111 | * @param x1 x coordinate of the first point 112 | * @param y1 y coordinate of the first point 113 | * @param x2 x coordinate of the second point 114 | * @param y2 y coordinate of the second point 115 | */ 116 | void DrawTile(DaisyPatch* patch, 117 | int x1, 118 | int y1, 119 | int x2, 120 | int y2); 121 | /* 122 | * Sets sprite pixels to off. 123 | */ 124 | void SetBlank(); 125 | 126 | /* 127 | * Sets the x axis shift. 128 | * 129 | * @param x shift value for the x axis 130 | */ 131 | void SetXShift(int x); 132 | 133 | /* 134 | * Sets the y axis shift. 135 | * 136 | * @param y shift value for the x axis 137 | */ 138 | void SetYShift(int y); 139 | 140 | /* 141 | * Adjusts x axis shift by the given amount. 142 | * 143 | * @param x shift of x axis 144 | */ 145 | void AdjustXShift(int x); 146 | 147 | /* 148 | * Adjusts y axis shift by the given amount. 149 | * 150 | * @param y shift of x axis 151 | */ 152 | void AdjustYShift(int y); 153 | }; 154 | 155 | #endif // DROPLETS_GRAPHICS_SPRITE_H_ 156 | -------------------------------------------------------------------------------- /src/droplets/lfo_droplet.cpp: -------------------------------------------------------------------------------- 1 | #include "lfo_droplet.h" 2 | 3 | void LFO::Init(DaisyPatch* m_patch, 4 | float samplerate, 5 | AnalogControl freqKnob, 6 | AnalogControl ampKnob) { 7 | patch = m_patch; 8 | osc.Init(samplerate); 9 | osc.SetAmp(1); 10 | wave = 0; 11 | freqCtrl.Init(freqKnob, 0.1f, 35.0f, Parameter::LOGARITHMIC); 12 | ampCtrl.Init(ampKnob, 0.0f, 1.0f, Parameter::LINEAR); 13 | } 14 | 15 | void LFO::Process(DacHandle::Channel chn) { 16 | osc.SetFreq(freqCtrl.Process()); 17 | osc.SetWaveform(wave); 18 | 19 | patch->seed.dac.WriteValue(chn, 20 | GetSignal() * ampCtrl.Process() * 4095.f); 21 | } 22 | 23 | void LFO::UpdateWave(int change) { 24 | wave = (MAX_WAVE + wave + change) % MAX_WAVE; 25 | } 26 | 27 | uint8_t LFO::GetWave() { 28 | return wave; 29 | } 30 | 31 | float LFO::GetSignal() { 32 | return (osc.Process()+ 1.0f) /2; 33 | } 34 | 35 | LFODroplet::LFODroplet(DaisyPatch* m_patch, 36 | DropletState m_state, 37 | float m_sample_rate) : 38 | Droplet(m_patch, 39 | m_state) { 40 | sample_rate = m_sample_rate; 41 | SetAnimationRate(5); 42 | CreateTitleGraph(); 43 | SetControls(); 44 | } 45 | 46 | LFODroplet::~LFODroplet() { 47 | delete title_graph; 48 | } 49 | 50 | void LFODroplet::CreateTitleGraph() { 51 | title_graph = new Graph(GetScreenMax()-GetScreenMin(), 52 | GetTitleHeight()); 53 | } 54 | 55 | void LFODroplet::Control() { 56 | Patch()->ProcessAnalogControls(); 57 | Patch()->encoder.Debounce(); 58 | lfo[0].UpdateWave(Patch()->encoder.Increment()); 59 | if (IsFull()) { 60 | lfo[1].UpdateWave(Patch()->encoder.Increment()); 61 | } 62 | } 63 | 64 | void LFODroplet::Process(AudioHandle::InputBuffer in, 65 | AudioHandle::OutputBuffer out, 66 | size_t size) { 67 | Patch()->ProcessAnalogControls(); 68 | 69 | for(size_t i = 0; i < size; i++) { 70 | if (IsRight()) { 71 | lfo[0].Process(DacHandle::Channel::TWO); 72 | } else { 73 | lfo[0].Process(DacHandle::Channel::ONE); 74 | } 75 | if (IsFull()) { 76 | lfo[1].Process(DacHandle::Channel::TWO); 77 | } 78 | } 79 | } 80 | 81 | void LFODroplet::Draw() { 82 | if (IsFull()) { 83 | WriteCenteredString(Patch(), 84 | (GetScreenMax()-GetScreenMin())/2, 85 | 54, 86 | Font_6x8, 87 | WaveToString(lfo[0].GetWave())); 88 | } else { 89 | WriteDoubleCentered(Patch(), 90 | GetScreenMin() + 91 | (GetScreenMax()-GetScreenMin())/2, 92 | 54, 93 | GetScreenMax()-GetScreenMin(), 94 | Font_6x8, 95 | WaveToString(lfo[0].GetWave())); 96 | } 97 | 98 | if(NeedUpdate()) { 99 | title_graph->Update(); 100 | } 101 | title_graph->SetPixelPercentage(lfo[0].GetSignal()); 102 | title_graph->Draw(Patch(), GetScreenMin(), 0); 103 | 104 | DrawName("LFO"); 105 | AnimationInc(); 106 | } 107 | 108 | void LFODroplet::UpdateStateCallback() { 109 | delete title_graph; 110 | CreateTitleGraph(); 111 | } 112 | 113 | void LFODroplet::SetControls() { 114 | switch (GetState()) { 115 | default: 116 | case DropletState::kFull: 117 | lfo[0].Init(Patch(), 118 | sample_rate, 119 | Patch()->controls[Patch()->CTRL_1], 120 | Patch()->controls[Patch()->CTRL_2]); 121 | lfo[1].Init(Patch(), 122 | sample_rate, 123 | Patch()->controls[Patch()->CTRL_3], 124 | Patch()->controls[Patch()->CTRL_4]); 125 | break; 126 | case DropletState::kLeft: 127 | lfo[0].Init(Patch(), 128 | sample_rate, 129 | Patch()->controls[Patch()->CTRL_1], 130 | Patch()->controls[Patch()->CTRL_2]); 131 | break; 132 | case DropletState::kRight: 133 | lfo[0].Init(Patch(), 134 | sample_rate, 135 | Patch()->controls[Patch()->CTRL_3], 136 | Patch()->controls[Patch()->CTRL_4]); 137 | break; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/droplets/ad_droplet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef DROPLETS_AD_DROPLET_H_ 4 | #define DROPLETS_AD_DROPLET_H_ 5 | 6 | #include "daisysp.h" 7 | #include "daisy_patch.h" 8 | 9 | #include "droplet.h" 10 | #include "../util.h" 11 | #include "../graphics/graph.h" 12 | 13 | using namespace daisy; 14 | using namespace daisysp; 15 | 16 | class AD { 17 | private: 18 | AdEnv env; 19 | float attack, decay, curve = 0; 20 | float amp = 1.0f; 21 | Parameter attack_param; 22 | Parameter decay_param; 23 | Parameter curve_param; 24 | Parameter amp_param; 25 | float sig; 26 | DaisyPatch* patch; 27 | bool curve_menu = false; 28 | DropletState* state; 29 | 30 | public: 31 | /* 32 | * Contructor for attach decay envelope. 33 | * 34 | * @param m_patch pointer to patch 35 | * @param sample_rate audio sample rate 36 | * @param state droplet position 37 | */ 38 | void Init(DaisyPatch* m_patch, 39 | float sample_rate, 40 | DropletState* state); 41 | 42 | /* 43 | * Precesses input from the trigger gate 44 | * and sends envelope data to a given CV output. 45 | * 46 | * @param chn 47 | * @param gate 48 | */ 49 | void Process(DacHandle::Channel chn, 50 | DaisyPatch::GateInput gate); 51 | 52 | /* 53 | * Returns the envelope signal level. 54 | * 55 | * @return envelope signal level 56 | */ 57 | float GetSignal(); 58 | 59 | /* 60 | * Returns the envelope attack time. 61 | * 62 | * @return envelope attack time 63 | */ 64 | float GetAttack(); 65 | 66 | /* 67 | * Returns the envelope decay time. 68 | * 69 | * @return envelope decay time 70 | */ 71 | float GetDecay(); 72 | 73 | /* 74 | * Returns the envelope slope shape. 75 | * 76 | * @return envelope slope shape 77 | */ 78 | float GetCurve(); 79 | 80 | /* 81 | * Returns the envelope amp level. 82 | * 83 | * @return envelope amp level 84 | */ 85 | float GetAmp(); 86 | 87 | /* 88 | * Returns the envelope menu state. 89 | * 90 | * @return envelope menu state 91 | */ 92 | bool GetMenu(); 93 | 94 | /* 95 | * Toggles envelope menu state. 96 | */ 97 | void ToggleCurve(); 98 | 99 | /* 100 | * Binds envelope to menu hardware controls. 101 | */ 102 | void SetControls(); 103 | }; 104 | 105 | class ADDroplet: public Droplet { 106 | private: 107 | AD ad[2]; 108 | float sample_rate; 109 | Graph* title_graph; 110 | 111 | /* 112 | * Create a new graph for the title bar. 113 | */ 114 | void CreateTitleGraph(); 115 | 116 | public: 117 | /* 118 | * Constructor for a AD droplet. 119 | * 120 | * @param m_patch pointer to patch 121 | * @param m_state droplet position 122 | * @param sample_rate audio sample rate 123 | */ 124 | ADDroplet(DaisyPatch* m_patch, 125 | DropletState m_state, 126 | float sample_rate); 127 | 128 | /* 129 | * Destructor for vco droplet. 130 | */ 131 | ~ADDroplet(); 132 | 133 | /* 134 | * Processes user controls and inputs. 135 | */ 136 | void Control(); 137 | 138 | /* 139 | * Processes audio input and outputs. 140 | * 141 | * @param in the audio inputs for the patch 142 | * @param out the audio outputs for the patch 143 | * @param size the number of inputs and outputs 144 | */ 145 | void Process(AudioHandle::InputBuffer in, 146 | AudioHandle::OutputBuffer out, 147 | size_t size); 148 | 149 | /* 150 | * Processes information to be shown on the display. 151 | */ 152 | void Draw(); 153 | 154 | /* 155 | * Runs when droplet state is updated. 156 | */ 157 | void UpdateStateCallback(); 158 | 159 | /* 160 | * Set up the controls for the droplet. 161 | */ 162 | void SetControls(); 163 | }; 164 | 165 | #endif // DROPLETS_AD_DROPLET_H_ 166 | -------------------------------------------------------------------------------- /src/menu.cpp: -------------------------------------------------------------------------------- 1 | #include "menu.h" 2 | 3 | Menu::Menu(DaisyPatch* m_patch, 4 | DropletState m_state, 5 | DropletManager* m_manager) { 6 | patch = m_patch; 7 | state = m_state; 8 | manager = m_manager; 9 | 10 | head = new MenuItem(MenuState::kSplit, "Split"); 11 | head->AddItemEnd(new MenuItem(MenuState::kChange, "")); 12 | head->AddItemEnd(new MenuItem(MenuState::kAD, "AD")); 13 | head->AddItemEnd(new MenuItem(MenuState::kLadderFilter, "Ladder Filter")); 14 | head->AddItemEnd(new MenuItem(MenuState::kLFO, "LFO")); 15 | head->AddItemEnd(new MenuItem(MenuState::kMixer, "Mixer")); 16 | head->AddItemEnd(new MenuItem(MenuState::kNoise, "Noise")); 17 | head->AddItemEnd(new MenuItem(MenuState::kSequencer, "Sequencer")); 18 | head->AddItemEnd(new MenuItem(MenuState::kVCA, "VCA")); 19 | head->AddItemEnd(new MenuItem(MenuState::kVCO, "VCO")); 20 | 21 | selected = head; 22 | buffer = selected; 23 | highlighted = selected; 24 | 25 | if (state == DropletState::kLeft) { 26 | head->SetStateTitle(MenuState::kChange, "Right"); 27 | } else if (state == DropletState::kRight) { 28 | head->SetStateTitle(MenuState::kChange, "Left"); 29 | } 30 | this->UpdateMenuState(); 31 | } 32 | 33 | bool Menu::InMenu() { 34 | return this->inMenu; 35 | } 36 | 37 | void Menu::SetInMenu(bool menuState) { 38 | inMenu = menuState; 39 | } 40 | 41 | void Menu::CreateMenuItem(std::string text, 42 | int position, 43 | bool highlighted) { 44 | text.insert(text.end(), MAX_CHAR_LENGTH-text.size(), ' '); 45 | if (highlighted) { 46 | DrawSolidRect(patch, 0, MENU_Y[2], 47 | SSD1309_WIDTH, MENU_Y[2]+17, true); 48 | WriteString(patch, MENU_X[position-1], MENU_Y[position-1], 49 | Font_11x18, text, !highlighted); 50 | } else { 51 | WriteString(patch, MENU_X[position-1], MENU_Y[position-1], 52 | Font_7x10, text, !highlighted); 53 | } 54 | } 55 | 56 | void Menu::ProcessMenuOled() { 57 | MenuItem* ptr; 58 | // Item 3 Highlighted 59 | CreateMenuItem(highlighted->GetTitle(), 3, true); 60 | 61 | // Item 2 62 | ptr = highlighted->GetPreviousVisible(); 63 | if (ptr == NULL) { 64 | CreateMenuItem("", 2, false); 65 | } else { 66 | CreateMenuItem(ptr->GetTitle(), 2, false); 67 | ptr = ptr->GetPreviousVisible(); 68 | } 69 | // Item 1 70 | if (ptr == NULL) { 71 | CreateMenuItem("", 1, false); 72 | } else { 73 | CreateMenuItem(ptr->GetTitle(), 1, false); 74 | } 75 | 76 | // Item 4 77 | ptr = highlighted->GetNextVisible(); 78 | if (ptr == NULL) { 79 | CreateMenuItem("", 4, false); 80 | } else { 81 | CreateMenuItem(ptr->GetTitle(), 4, false); 82 | ptr = ptr->GetNextVisible(); 83 | } 84 | 85 | // Item 5 86 | if (ptr == NULL) { 87 | CreateMenuItem("", 5, false); 88 | } else { 89 | CreateMenuItem(ptr->GetTitle(), 5, false); 90 | } 91 | } 92 | 93 | void Menu::UpdateMenuPosition() { 94 | int move = patch->encoder.Increment(); 95 | if (move < 0) { 96 | if (highlighted->GetNextVisible() != NULL) { 97 | highlighted = highlighted->GetNextVisible(); 98 | } 99 | } else if (move > 0) { 100 | if (highlighted->GetPreviousVisible() != NULL) { 101 | highlighted = highlighted->GetPreviousVisible(); 102 | } 103 | } 104 | } 105 | 106 | std::string Menu::SelectedName() { 107 | return highlighted->GetTitle(); 108 | } 109 | 110 | MenuState Menu::GetState() { 111 | return highlighted->GetState(); 112 | } 113 | 114 | void Menu::SetState(MenuState state) { 115 | MenuItem* ptr = head; 116 | while(ptr != NULL) { 117 | if (ptr->GetState() == state) { 118 | Select(ptr); 119 | return; 120 | } 121 | ptr = ptr->GetNext(); 122 | } 123 | } 124 | 125 | MenuState Menu::GetBufferState() { 126 | return buffer->GetState(); 127 | } 128 | 129 | void Menu::UpdateMenuState() { 130 | if (manager->GetSplitMode()) { 131 | head->SetStateVisibility(MenuState::kChange, true); 132 | head->SetStateTitle(MenuState::kSplit, "Merge"); 133 | } else { 134 | head->SetStateVisibility(MenuState::kChange, false); 135 | head->SetStateTitle(MenuState::kSplit, "Split"); 136 | } 137 | } 138 | 139 | void Menu::Select() { 140 | Select(highlighted); 141 | } 142 | 143 | void Menu::Select(MenuItem* item) { 144 | if (selected->GetState() != MenuState::kSplit || 145 | selected->GetState() != MenuState::kChange) { 146 | buffer = selected; 147 | } 148 | 149 | selected = item; 150 | } 151 | -------------------------------------------------------------------------------- /src/droplets/vco_droplet.cpp: -------------------------------------------------------------------------------- 1 | #include "vco_droplet.h" 2 | 3 | VCODroplet::VCODroplet(DaisyPatch* m_patch, 4 | DropletState m_state, 5 | float sample_rate) : 6 | Droplet(m_patch, 7 | m_state) { 8 | SetAnimationRate(10); 9 | osc.Init(sample_rate); 10 | 11 | wave = Oscillator::WAVE_SAW; 12 | 13 | SetControls(); 14 | } 15 | 16 | VCODroplet::~VCODroplet() { 17 | delete wave_graphic; 18 | } 19 | 20 | void VCODroplet::Control() { 21 | Patch()->ProcessAnalogControls(); 22 | Patch()->encoder.Debounce(); 23 | AdjustWaveShape(Patch()->encoder.Increment()); 24 | } 25 | 26 | void VCODroplet::Process(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { 27 | float sig, freq, amp = 1.0; 28 | 29 | Patch()->ProcessAnalogControls(); 30 | 31 | for (size_t i = 0; i < size; i++) { 32 | // Read Knobs 33 | freq = mtof(freqctrl.Process() + finectrl.Process()); 34 | if (IsFull()) { 35 | if((size_t) wavectrl.Process() != last_wave_ctrl) { 36 | AdjustWaveShape((size_t)wavectrl.Process()-last_wave_ctrl); 37 | last_wave_ctrl = wavectrl.Process(); 38 | } 39 | amp = ampctrl.Process(); 40 | } 41 | // Set osc params 42 | osc.SetFreq(freq); 43 | osc.SetWaveform(wave); 44 | osc.SetAmp(amp); 45 | // Process 46 | sig = osc.Process(); 47 | // Assign Synthesized Waveform to outputs. 48 | for (size_t chn = GetChannelMin(); chn < GetChannelMax(); chn++) { 49 | out[chn][i] = sig; 50 | } 51 | } 52 | } 53 | 54 | void VCODroplet::Draw() { 55 | SetWaveState(wave); 56 | if (IsFull()) { 57 | WriteCenteredString(Patch(), 58 | (GetScreenMax()-GetScreenMin())/2, 59 | 54, 60 | Font_6x8, 61 | WaveToString(wave)); 62 | } else { 63 | WriteDoubleCentered(Patch(), 64 | GetScreenMin() + 65 | (GetScreenMax()-GetScreenMin())/2, 66 | 54, 67 | GetScreenMax()-GetScreenMin(), 68 | Font_6x8, 69 | WaveToString(wave)); 70 | } 71 | wave_graphic->DrawTile(Patch(), 72 | GetScreenMin(), 73 | 0, 74 | GetScreenMax(), 75 | GetTitleHeight()); 76 | if(NeedUpdate()) { 77 | wave_graphic->AdjustXShift(1); 78 | } 79 | DrawName("VCO"); 80 | AnimationInc(); 81 | } 82 | 83 | void VCODroplet::SetWaveState(uint8_t wf) { 84 | switch(wf){ 85 | case Oscillator::WAVE_TRI: 86 | wave_graphic->SetWaveShape(WaveShape::kTriangle); 87 | return; 88 | case Oscillator::WAVE_SQUARE: 89 | wave_graphic->SetWaveShape(WaveShape::kSquare); 90 | return; 91 | case Oscillator::WAVE_SIN: 92 | wave_graphic->SetWaveShape(WaveShape::kSine); 93 | return; 94 | case Oscillator::WAVE_SAW: 95 | wave_graphic->SetWaveShape(WaveShape::kSaw); 96 | return; 97 | case Oscillator::WAVE_RAMP: 98 | wave_graphic->SetWaveShape(WaveShape::kRamp); 99 | return; 100 | case Oscillator::WAVE_POLYBLEP_TRI: 101 | wave_graphic->SetWaveShape(WaveShape::kTriangle); 102 | return; 103 | case Oscillator::WAVE_POLYBLEP_SQUARE: 104 | wave_graphic->SetWaveShape(WaveShape::kSquare); 105 | return; 106 | default: 107 | case Oscillator::WAVE_POLYBLEP_SAW: 108 | wave_graphic->SetWaveShape(WaveShape::kSaw); 109 | return; 110 | } 111 | } 112 | 113 | void VCODroplet::AdjustWaveShape(int amount) { 114 | wave = (Oscillator::WAVE_LAST + wave + amount) % 115 | Oscillator::WAVE_LAST; 116 | } 117 | 118 | void VCODroplet::SetWaveShape(int ws) { 119 | wave = ws % Oscillator::WAVE_LAST; 120 | last_wave_ctrl = ws; 121 | } 122 | 123 | void VCODroplet::UpdateStateCallback() { 124 | SetControls(); 125 | } 126 | 127 | void VCODroplet::SetControls() { 128 | DaisyPatch::Ctrl freq, fine; 129 | switch (GetState()){ 130 | default: 131 | case DropletState::kFull: 132 | wavectrl.Init(Patch()->controls[Patch()->CTRL_3], 133 | 0.0, 134 | Oscillator::WAVE_LAST, 135 | Parameter::LINEAR); 136 | ampctrl.Init(Patch()->controls[Patch()->CTRL_4], 137 | 0.0, 138 | 0.5f, 139 | Parameter::LINEAR); 140 | freq = Patch()->CTRL_1; 141 | fine = Patch()->CTRL_2; 142 | SetWaveShape(wavectrl.Process()); 143 | break; 144 | case DropletState::kLeft: 145 | freq = Patch()->CTRL_1; 146 | fine = Patch()->CTRL_2; 147 | break; 148 | case DropletState::kRight: 149 | freq = Patch()->CTRL_3; 150 | fine = Patch()->CTRL_4; 151 | break; 152 | } 153 | freqctrl.Init(Patch()->controls[freq], 10.0, 154 | 110.0f, Parameter::LINEAR); 155 | finectrl.Init(Patch()->controls[fine], 0.f, 156 | 7.f, Parameter::LINEAR); 157 | } 158 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | using namespace daisy; 4 | 5 | int main(void) { 6 | patch.Init(); 7 | sample_rate = patch.AudioSampleRate(); 8 | selected_menu = left_menu; 9 | droplet_left = GetDroplet(DropletState::kFull, 10 | selected_menu->GetState()); 11 | 12 | patch.StartAdc(); 13 | patch.StartAudio(AudioThrough); 14 | 15 | while(true) { 16 | ProcessControls(); 17 | ProcessOled(); 18 | ProcessOutputs(); 19 | } 20 | } 21 | 22 | void ProcessControls() { 23 | patch.ProcessAnalogControls(); 24 | patch.encoder.Debounce(); 25 | // Handle menu interactions 26 | if (selected_menu->InMenu()) { 27 | selected_menu->UpdateMenuPosition(); 28 | // Handle menu selection 29 | if (patch.encoder.RisingEdge()) { 30 | selected_menu->SetInMenu(false); 31 | selected_menu->Select(); 32 | // Split selected 33 | if(selected_menu->GetState() == MenuState::kSplit) { 34 | manager->ToggleSplit(); 35 | left_menu->UpdateMenuState(); 36 | right_menu->UpdateMenuState(); 37 | // Enable split 38 | if (manager->GetSplitMode()) { 39 | droplet_left->UpdateState(DropletState::kLeft); 40 | droplet_right = GetDroplet(DropletState::kRight, 41 | left_menu->GetBufferState()); 42 | } 43 | // Merge 44 | else { 45 | if (selected_menu == left_menu) { 46 | droplet_left->UpdateState(DropletState::kFull); 47 | delete droplet_right; 48 | } else { 49 | droplet_right->UpdateState(DropletState::kFull); 50 | delete droplet_left; 51 | droplet_left = droplet_right; 52 | left_menu->SetState(right_menu->GetBufferState()); 53 | selected_menu = left_menu; 54 | } 55 | } 56 | } 57 | // Switch side 58 | else if (selected_menu->GetState() == MenuState::kChange) { 59 | if (selected_menu == left_menu) { 60 | selected_menu = right_menu; 61 | } else { 62 | selected_menu = left_menu; 63 | } 64 | } 65 | // Enable new mode 66 | else { 67 | if(manager->GetSplitMode()) { 68 | if (selected_menu == left_menu) { 69 | delete droplet_left; 70 | droplet_left = GetDroplet(DropletState::kLeft, 71 | selected_menu->GetState()); 72 | } else { 73 | delete droplet_right; 74 | droplet_right = GetDroplet(DropletState::kRight, 75 | selected_menu->GetState()); 76 | } 77 | } else { 78 | delete droplet_left; 79 | droplet_left = GetDroplet(DropletState::kFull, 80 | selected_menu->GetState()); 81 | } 82 | } 83 | } 84 | } 85 | // Check if entering menu 86 | else { 87 | if (patch.encoder.Pressed()) { 88 | if (patch.encoder.TimeHeldMs() > 500 && 89 | patch.encoder.TimeHeldMs() < 505) { 90 | selected_menu->SetInMenu(true); 91 | } 92 | } 93 | } 94 | } 95 | 96 | void ProcessOutputs() { 97 | if(!selected_menu->InMenu()) { 98 | if (manager->GetSplitMode() && 99 | selected_menu == right_menu) { 100 | droplet_right->Control(); 101 | } else { 102 | droplet_left->Control(); 103 | } 104 | } 105 | } 106 | 107 | void ProcessOled() { 108 | patch.display.Fill(false); 109 | if (selected_menu->InMenu()) { 110 | selected_menu->ProcessMenuOled(); 111 | } else { 112 | droplet_left->Draw(); 113 | if (manager->GetSplitMode()) { 114 | droplet_right->Draw(); 115 | } 116 | } 117 | patch.display.Update(); 118 | } 119 | 120 | static void AudioThrough(AudioHandle::InputBuffer in, 121 | AudioHandle::OutputBuffer out, 122 | size_t size) { 123 | droplet_left->Process(in, out, size); 124 | if (manager->GetSplitMode()) { 125 | droplet_right->Process(in, out, size); 126 | } 127 | } 128 | 129 | Droplet* GetDroplet(DropletState state, 130 | MenuState menu) { 131 | switch(menu) { 132 | 133 | case MenuState::kAD: 134 | return new ADDroplet(&patch, 135 | state, 136 | sample_rate); 137 | case MenuState::kLadderFilter: 138 | return new LadderFilterDroplet(&patch, 139 | state, 140 | sample_rate); 141 | case MenuState::kLFO: 142 | return new LFODroplet(&patch, 143 | state, 144 | sample_rate); 145 | case MenuState::kMixer: 146 | return new MixerDroplet(&patch, 147 | state); 148 | case MenuState::kNoise: 149 | return new NoiseDroplet(&patch, 150 | state); 151 | default: 152 | case MenuState::kSequencer: 153 | return new SequencerDroplet(&patch, 154 | state, 155 | sample_rate); 156 | case MenuState::kVCA: 157 | return new VCADroplet(&patch, 158 | state); 159 | case MenuState::kVCO: 160 | return new VCODroplet(&patch, 161 | state, 162 | sample_rate); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/droplets/droplet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef CASCADE_DROPLETS_DROPLET_H_ 4 | #define CASCADE_DROPLETS_DROPLET_H_ 5 | 6 | #include "daisy_patch.h" 7 | 8 | #include 9 | 10 | #include "../util.h" 11 | 12 | using namespace daisy; 13 | 14 | enum class DropletState {kFull, kLeft, kRight}; 15 | 16 | class Droplet { 17 | private: 18 | DaisyPatch* patch; 19 | DropletState state; 20 | const int kTitleHeight = 7; 21 | unsigned int count = 0; 22 | unsigned int animation_rate = 1; 23 | unsigned int animation_count = 0; 24 | int screen_min; 25 | int screen_max; 26 | size_t chn_min = 0; 27 | size_t chn_max = 4; 28 | public: 29 | /* 30 | * Constructor for a droplet which is a mode for either half or full 31 | * control for the daisy patch. 32 | * 33 | * @param m_patch pointer to patch 34 | * @param m_state droplet position 35 | */ 36 | Droplet(DaisyPatch* m_patch, DropletState m_state); 37 | 38 | /* 39 | * Destructor for a droplet. 40 | */ 41 | virtual ~Droplet() {}; 42 | 43 | /* 44 | * Processes user controls and inputs. 45 | */ 46 | virtual void Control()=0; 47 | 48 | /* 49 | * Processes audio input and outputs. 50 | 2 * 51 | * @param in the audio inputs for the patch 52 | * @param out the audio outputs for the patch 53 | * @param size the number of inputs and outputs 54 | */ 55 | virtual void Process(AudioHandle::InputBuffer in, 56 | AudioHandle::OutputBuffer out, 57 | size_t size)=0; 58 | 59 | /* 60 | * Processes information to be shown on the display. 61 | */ 62 | virtual void Draw()=0; 63 | 64 | /* 65 | * Runs when droplet state is updated. 66 | */ 67 | virtual void UpdateStateCallback() {} 68 | 69 | /* 70 | * Set up the controls for the droplet. 71 | */ 72 | virtual void SetControls() {} 73 | 74 | /* 75 | * Returns patch. 76 | * 77 | * @return pointer to patch 78 | */ 79 | DaisyPatch* Patch(); 80 | 81 | /* 82 | * Returns droplet state. 83 | * 84 | * @return pointer to state 85 | */ 86 | DropletState* State(); 87 | 88 | /* 89 | * Returns the size of the droplet. 90 | * 91 | * @return size of droplet 92 | */ 93 | DropletState GetState(); 94 | 95 | /* 96 | * Returns if the droplet is in the left state. 97 | * 98 | * @ return droplet in left state 99 | */ 100 | bool IsLeft(); 101 | 102 | /* 103 | * Returns if the droplet is in the right state. 104 | * 105 | * @ return droplet in right state 106 | */ 107 | bool IsRight(); 108 | 109 | /* 110 | * Returns if the droplet is in the full state. 111 | * 112 | * @ return droplet in full state 113 | */ 114 | bool IsFull(); 115 | 116 | /* 117 | * Returns the height of the title bar of the droplet. 118 | * 119 | * @return height of title bar 120 | */ 121 | int GetTitleHeight(); 122 | 123 | /* 124 | * Returns the width of the droplet. 125 | * 126 | * @return screen width of the droplet 127 | */ 128 | int GetScreenWidth(); 129 | 130 | /* 131 | * Returns the minimum screen position based on droplet size. 132 | * 133 | * @return droplet minimum screen position 134 | */ 135 | int GetScreenMin(); 136 | 137 | /* 138 | * Returns the maximum screen position based on droplet size. 139 | * 140 | * @return droplet maximum screen position 141 | */ 142 | int GetScreenMax(); 143 | 144 | /* 145 | * Returns the minimum channel position based on droplet size. 146 | * 147 | * @return droplet minimum channel position 148 | */ 149 | size_t GetChannelMin(); 150 | 151 | /* 152 | * Returns the maximum channel position based on droplet size. 153 | * 154 | * @return droplet maximum channel position 155 | */ 156 | size_t GetChannelMax(); 157 | 158 | /* 159 | * Draws droplet name on the title bar. 160 | * 161 | * @param name droplet position 162 | */ 163 | void DrawName(std::string name); 164 | 165 | /* 166 | * Changes droplet position. 167 | * 168 | * @param m_state droplet 169 | */ 170 | void UpdateState(DropletState m_state); 171 | 172 | /* 173 | * Updates animation clock. 174 | */ 175 | void AnimationInc(); 176 | 177 | /* 178 | * Sets animation refresh rate 179 | * 180 | * @param rate display refrash rate 181 | */ 182 | void SetAnimationRate(int rate); 183 | 184 | /* 185 | * Returns a count for total amount of animation frames. 186 | * 187 | * @return animation count 188 | */ 189 | int GetAnimationCount(); 190 | 191 | /* 192 | * Returns if display needs a refresh. 193 | * 194 | * @return display in need of update 195 | */ 196 | bool NeedUpdate(); 197 | }; 198 | 199 | #endif // CASCADE_DROPLETS_DROPLET_H_ 200 | -------------------------------------------------------------------------------- /src/util.cpp: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | void DrawSolidRect(DaisyPatch* patch, 4 | uint8_t x1, 5 | uint8_t y1, 6 | uint8_t x2, 7 | uint8_t y2, 8 | bool on) { 9 | for (int i = std::min(y1, y2); i <= std::max(y1, y2); i++) { 10 | patch->display.DrawLine(x1, i, x2, i, on); 11 | } 12 | } 13 | 14 | void WriteString(DaisyPatch* patch, 15 | int x, 16 | int y, 17 | FontDef font, 18 | std::string text, 19 | bool on) { 20 | patch->display.SetCursor(x, y); 21 | patch->display.WriteString(&text[0], font, on); 22 | } 23 | 24 | void WriteString(DaisyPatch* patch, 25 | int x, 26 | int y, 27 | FontDef font, 28 | std::string text) { 29 | WriteString(patch, x, y, font, text, true); 30 | } 31 | 32 | void WriteString(DaisyPatch* patch, 33 | int x, 34 | int y, 35 | std::string text) { 36 | WriteString(patch, x, y, text, true); 37 | } 38 | 39 | void WriteString(DaisyPatch* patch, 40 | int x, 41 | int y, 42 | std::string text, 43 | bool on) { 44 | WriteString(patch, x, y, Font_6x8, text, on); 45 | } 46 | 47 | void WriteCenteredString(DaisyPatch* patch, 48 | int x, 49 | int y, 50 | FontDef font, 51 | std::string text, 52 | bool on) { 53 | 54 | int text_width = font.FontWidth * text.size(); 55 | WriteString(patch, x - (text_width / 2), y, font, text, on); 56 | } 57 | 58 | void WriteCenteredString(DaisyPatch* patch, 59 | int x, 60 | int y, 61 | FontDef font, 62 | std::string text) { 63 | WriteCenteredString(patch, x, y, font, text, true); 64 | } 65 | 66 | void WriteDoubleCentered(DaisyPatch* patch, 67 | int x, 68 | int y, 69 | int width, 70 | FontDef font, 71 | std::string text, 72 | bool on) { 73 | // If only one line is needed 74 | if ((int) text.length() * font.FontWidth < width) { 75 | WriteCenteredString(patch, 76 | x, 77 | y - font.FontHeight/2, 78 | font, 79 | text, 80 | on); 81 | } else { 82 | unsigned int split = text.find(" "); 83 | if (split == std::string::npos) { 84 | split = width / font.FontWidth; 85 | } 86 | std::string row1 = text.substr(0, split); 87 | std::string row2 = text.substr(split+1, text.length()); 88 | WriteCenteredString(patch, 89 | x, 90 | y - font.FontHeight, 91 | font, 92 | row1, 93 | on); 94 | WriteCenteredString(patch, 95 | x, 96 | y, 97 | font, 98 | row2, 99 | on); 100 | } 101 | } 102 | 103 | void WriteDoubleCentered(DaisyPatch* patch, 104 | int x, 105 | int y, 106 | int width, 107 | FontDef font, 108 | std::string text) { 109 | WriteDoubleCentered(patch, x, y, width, font, text, true); 110 | } 111 | 112 | std::string WaveToString(uint8_t wf) { 113 | switch(wf){ 114 | case Oscillator::WAVE_TRI: 115 | return "Triangle"; 116 | case Oscillator::WAVE_SQUARE: 117 | return "Square"; 118 | case Oscillator::WAVE_SIN: 119 | return "Sine"; 120 | case Oscillator::WAVE_SAW: 121 | return "Saw"; 122 | case Oscillator::WAVE_RAMP: 123 | return "Ramp"; 124 | case Oscillator::WAVE_POLYBLEP_TRI: 125 | return "PolyBLEP Triangle"; 126 | case Oscillator::WAVE_POLYBLEP_SQUARE: 127 | return "PolyBLEP Square"; 128 | case Oscillator::WAVE_POLYBLEP_SAW: 129 | return "PolyBLEP Saw"; 130 | } 131 | return ""; 132 | } 133 | 134 | std::string FloatToString(float num, int places) { 135 | std::string sign = ""; 136 | int integral = static_cast(num); 137 | int fractional = static_cast((abs(num) - abs(integral)) 138 | * pow(10, places)); 139 | if (num < 0.0f && num > -1.0f) { 140 | sign = "-"; 141 | } 142 | return sign + std::to_string(integral) + "." + std::to_string(fractional); 143 | } 144 | 145 | void DrawTwoDividedRectangles(DaisyPatch* patch, 146 | int divider, 147 | int width_min, 148 | int width_max, 149 | int rect_one_min, 150 | int rect_one_max, 151 | int rect_two_min, 152 | int rect_two_max) { 153 | DrawSolidRect(patch, 154 | width_min, 155 | rect_one_max, 156 | width_min+divider, 157 | rect_one_min, 158 | true); 159 | DrawSolidRect(patch, 160 | width_max-divider, 161 | rect_two_max, 162 | width_max, 163 | rect_two_min, 164 | true); 165 | } 166 | 167 | void DrawFourDividedRectangles(DaisyPatch* patch, 168 | int divider, 169 | int width_min, 170 | int width_max, 171 | int rect_one_min, 172 | int rect_one_max, 173 | int rect_two_min, 174 | int rect_two_max, 175 | int rect_three_min, 176 | int rect_three_max, 177 | int rect_four_min, 178 | int rect_four_max) { 179 | DrawTwoDividedRectangles(patch, 180 | divider, 181 | width_min, 182 | width_max, 183 | rect_one_min, 184 | rect_one_max, 185 | rect_four_min, 186 | rect_four_max); 187 | DrawTwoDividedRectangles(patch, 188 | divider, 189 | width_min+divider, 190 | width_max-divider, 191 | rect_two_min, 192 | rect_two_max, 193 | rect_three_min, 194 | rect_three_max); 195 | } 196 | 197 | int GetShiftArray(int pos, 198 | int shift, 199 | int array_size) { 200 | return (array_size + ((pos + shift) % array_size)) % array_size; 201 | } 202 | -------------------------------------------------------------------------------- /src/droplets/sequencer_droplet.cpp: -------------------------------------------------------------------------------- 1 | #include "sequencer_droplet.h" 2 | 3 | SequencerDroplet::SequencerDroplet(DaisyPatch* m_patch, 4 | DropletState m_state, 5 | float sample_rate) : 6 | Droplet(m_patch, 7 | m_state) { 8 | SetAnimationRate(20); 9 | CreateTitleGraph(); 10 | SetDimensions(); 11 | SetControls(); 12 | SetInMenu(); 13 | } 14 | 15 | SequencerDroplet::~SequencerDroplet() { 16 | delete title_graph; 17 | } 18 | 19 | void SequencerDroplet::Control() { 20 | Patch()->ProcessAnalogControls(); 21 | Patch()->encoder.Debounce(); 22 | AdjustSelected(Patch()->encoder.Increment()); 23 | } 24 | 25 | void SequencerDroplet::Process(AudioHandle::InputBuffer in, 26 | AudioHandle::OutputBuffer out, 27 | size_t size) { 28 | // Step input for full and left droplets 29 | if(!IsRight() && Patch()->gate_input[0].Trig()) { 30 | Step(); 31 | } 32 | 33 | // Step for right droplet and reset for full 34 | if(!IsLeft() && Patch()->gate_input[1].Trig()) { 35 | if (IsFull()) { 36 | Reset(); 37 | } else { 38 | Step(); 39 | } 40 | } 41 | 42 | // Limit control update rate to reduce noise 43 | if (control_rate_count == CONTROL_RATE_LIMIT) { 44 | for (size_t chn = GetChannelMin(); chn < GetChannelMax(); chn++) { 45 | // Check for control being moved 46 | if (std::abs(control[chn].Process() 47 | -last_control_value[chn]) > CONTROL_DEADZONE) { 48 | int right_offset = IsRight() ? -2 : 0; 49 | if (!InMenu() && (int) chn+selected*num_columns+right_offset < sequence_length) { 50 | sequence[chn+selected*num_columns+right_offset] = control[chn].Process(); 51 | } else { 52 | // Set sequence length 53 | if (chn == GetChannelMin()) { 54 | sequence_length = std::max(1.0f,control[chn].Process() / 55 | 4.85f*MAX_SEQUENCE_LENGTH); 56 | SetDimensions(); 57 | SetInMenu(); 58 | } 59 | // Set clock divider 60 | if (chn == GetChannelMin()+1) { 61 | divider = std::max(1.0f,control[chn].Process() / 62 | 4.85f*MAX_CLOCK_DIVIDE); 63 | } 64 | } 65 | } 66 | last_control_value[chn] = control[chn].Process(); 67 | } 68 | control_rate_count = 0; 69 | } 70 | control_rate_count++; 71 | 72 | // VC output of sequencer 73 | for(size_t i = 0; i < size; i++) { 74 | if (!IsRight()) { 75 | Patch()->seed.dac.WriteValue(DacHandle::Channel::ONE, 76 | sequence[step] * 819.2f); 77 | } 78 | if (!IsRight()) { 79 | Patch()->seed.dac.WriteValue(DacHandle::Channel::TWO, 80 | sequence[step] * 819.2f); 81 | } 82 | } 83 | } 84 | 85 | void SequencerDroplet::Draw() { 86 | int left_padding = 4+GetScreenMin(); 87 | int offset = step / (num_columns*NUM_ROWS); 88 | 89 | // Active Input 90 | if (!InMenu()) { 91 | offset = selected / NUM_ROWS; 92 | DrawSolidRect(Patch(), 93 | GetScreenMin(), 94 | 8+selected%NUM_ROWS*8, 95 | GetScreenMin()+2, 96 | 15+selected%NUM_ROWS*8, true); 97 | } 98 | offset *= num_columns*NUM_ROWS; 99 | 100 | // Notes 101 | for (int i = 0; i < num_columns*NUM_ROWS && i+offset < sequence_length; i++) { 102 | WriteString(Patch(), 103 | GetScreenWidth()/num_columns*(i%num_columns)+left_padding, 104 | 8+(std::floor(i/num_columns)*8), 105 | FloatToString(sequence[i+offset], 2), 106 | i+offset!=step); 107 | } 108 | 109 | // Draw info bar 110 | DrawSolidRect(Patch(),GetScreenMin(),56,GetScreenMax(),63, InMenu()); 111 | length_text = std::to_string(step+1); 112 | if (sequence_length >= 10) { 113 | length_text.insert(length_text.begin(), 2 - length_text.length(), '0'); 114 | } 115 | length_text.append("/"+std::to_string(sequence_length)); 116 | WriteString(Patch(), 117 | 2+GetScreenMin(), 118 | 56, 119 | length_text, 120 | !InMenu()); 121 | WriteString(Patch(), 122 | 36+GetScreenMin(), 123 | 56, 124 | std::to_string(divider), 125 | !InMenu()); 126 | 127 | if(NeedUpdate()) { 128 | title_graph->Update(); 129 | } 130 | title_graph->SetPixelPercentage(sequence[step]/5.0f); 131 | title_graph->Draw(Patch(), GetScreenMin(), 0); 132 | DrawName("Sequencer"); 133 | } 134 | 135 | void SequencerDroplet::UpdateStateCallback() { 136 | SetDimensions(); 137 | SetInMenu(); 138 | delete title_graph; 139 | CreateTitleGraph(); 140 | } 141 | 142 | void SequencerDroplet::SetControls() { 143 | control[0].Init(Patch()->controls[Patch()->CTRL_1], 144 | 0.0, 5.0f, Parameter::LINEAR); 145 | control[1].Init(Patch()->controls[Patch()->CTRL_2], 146 | 0.0, 5.0f, Parameter::LINEAR); 147 | control[2].Init(Patch()->controls[Patch()->CTRL_3], 148 | 0.0, 5.0f, Parameter::LINEAR); 149 | control[3].Init(Patch()->controls[Patch()->CTRL_4], 150 | 0.0, 5.0f, Parameter::LINEAR); 151 | } 152 | 153 | void SequencerDroplet::Step() { 154 | divider_count = (divider_count + 1) % divider; 155 | if (divider_count == 0) { 156 | step = (step + 1) % sequence_length; 157 | } 158 | } 159 | 160 | void SequencerDroplet::Reset() { 161 | step = 0; 162 | } 163 | 164 | void SequencerDroplet::SetDimensions() { 165 | if (!IsFull()) { 166 | num_columns = 2; 167 | } else { 168 | num_columns = 4; 169 | } 170 | num_rows = std::ceil((float)sequence_length/num_columns); 171 | } 172 | 173 | void SequencerDroplet::AdjustSelected(int adj) { 174 | selected = (num_rows+selected+adj+1) % (num_rows+1); 175 | } 176 | 177 | bool SequencerDroplet::InMenu() { 178 | return selected >= num_rows; 179 | } 180 | 181 | void SequencerDroplet::SetInMenu() { 182 | selected = 0; 183 | AdjustSelected(-1); 184 | } 185 | 186 | void SequencerDroplet::CreateTitleGraph() { 187 | title_graph = new Graph(GetScreenMax()-GetScreenMin(), 188 | GetTitleHeight()); 189 | } 190 | -------------------------------------------------------------------------------- /src/droplets/ad_droplet.cpp: -------------------------------------------------------------------------------- 1 | #include "ad_droplet.h" 2 | 3 | void AD::Init(DaisyPatch* m_patch, 4 | float sample_rate, 5 | DropletState* m_state) { 6 | patch = m_patch; 7 | state = m_state; 8 | env.Init(sample_rate); 9 | env.SetMax(1.0f); 10 | env.SetMin(0.0f); 11 | env.SetCurve(0.0f); 12 | 13 | SetControls(); 14 | } 15 | 16 | void AD::Process(DacHandle::Channel chn, 17 | DaisyPatch::GateInput gate) { 18 | if(patch->gate_input[gate].Trig()) { 19 | env.Trigger(); 20 | } 21 | 22 | if (*state == DropletState::kFull) { 23 | attack = attack_param.Process(); 24 | decay = decay_param.Process(); 25 | curve = curve_param.Process(); 26 | amp = amp_param.Process(); 27 | } else if (curve_menu) { 28 | curve = curve_param.Process(); 29 | amp = amp_param.Process(); 30 | } else { 31 | attack = attack_param.Process(); 32 | decay = decay_param.Process(); 33 | } 34 | 35 | env.SetTime(ADENV_SEG_ATTACK, attack); 36 | env.SetTime(ADENV_SEG_DECAY, decay); 37 | env.SetCurve(curve); 38 | 39 | sig = env.Process(); 40 | patch->seed.dac.WriteValue(chn, 41 | sig * amp * 4095.0f); 42 | } 43 | 44 | float AD::GetSignal() { 45 | return sig; 46 | } 47 | 48 | float AD::GetAttack() { 49 | return attack; 50 | } 51 | 52 | float AD::GetDecay() { 53 | return decay; 54 | } 55 | 56 | float AD::GetCurve() { 57 | return curve; 58 | } 59 | 60 | float AD::GetAmp() { 61 | return amp; 62 | } 63 | 64 | bool AD::GetMenu() { 65 | return curve_menu; 66 | } 67 | 68 | void AD::ToggleCurve() { 69 | curve_menu = !curve_menu; 70 | } 71 | 72 | void AD::SetControls() { 73 | AnalogControl attack_knob, decay_knob, curve_knob, amp_knob; 74 | 75 | switch (*state) { 76 | default: 77 | case DropletState::kFull: 78 | attack_knob = patch->controls[patch->CTRL_1]; 79 | decay_knob = patch->controls[patch->CTRL_2]; 80 | curve_knob = patch->controls[patch->CTRL_3]; 81 | amp_knob = patch->controls[patch->CTRL_4]; 82 | break; 83 | case DropletState::kLeft: 84 | attack_knob = patch->controls[patch->CTRL_1]; 85 | decay_knob = patch->controls[patch->CTRL_2]; 86 | curve_knob = patch->controls[patch->CTRL_1]; 87 | amp_knob = patch->controls[patch->CTRL_2]; 88 | break; 89 | case DropletState::kRight: 90 | attack_knob = patch->controls[patch->CTRL_3]; 91 | decay_knob = patch->controls[patch->CTRL_4]; 92 | curve_knob = patch->controls[patch->CTRL_3]; 93 | amp_knob = patch->controls[patch->CTRL_4]; 94 | break; 95 | } 96 | 97 | attack_param.Init(attack_knob, .01f, 3.0f, Parameter::EXPONENTIAL); 98 | decay_param.Init(decay_knob, .01f, 3.0f, Parameter::EXPONENTIAL); 99 | curve_param.Init(curve_knob, -10.f, 10.0f, Parameter::LINEAR); 100 | amp_param.Init(amp_knob, 0.0f, 1.0f, Parameter::LINEAR); 101 | } 102 | 103 | ADDroplet::ADDroplet(DaisyPatch* m_patch, 104 | DropletState m_state, 105 | float m_sample_rate) : 106 | Droplet(m_patch, 107 | m_state) { 108 | sample_rate = m_sample_rate; 109 | SetAnimationRate(10); 110 | ad[0].Init(Patch(), 111 | sample_rate, 112 | State()); 113 | if (IsFull()) { 114 | ad[1].Init(Patch(), 115 | sample_rate, 116 | State()); 117 | } 118 | CreateTitleGraph(); 119 | } 120 | 121 | ADDroplet::~ADDroplet() { 122 | delete title_graph; 123 | } 124 | 125 | void ADDroplet::CreateTitleGraph() { 126 | title_graph = new Graph(GetScreenMax()-GetScreenMin(), 127 | GetTitleHeight()); 128 | } 129 | 130 | void ADDroplet::Control() { 131 | if (Patch()->encoder.Pressed()) { 132 | if (Patch()->encoder.TimeHeldMs() < 10) { 133 | ad[0].ToggleCurve(); 134 | if (IsFull()) { 135 | ad[1].ToggleCurve(); 136 | } 137 | } 138 | } 139 | } 140 | 141 | void ADDroplet::Process(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { 142 | Patch()->ProcessAnalogControls(); 143 | for(size_t i = 0; i < size; i++) { 144 | if (IsRight()) { 145 | ad[0].Process(DacHandle::Channel::TWO, DaisyPatch::GATE_IN_2); 146 | } else { 147 | ad[0].Process(DacHandle::Channel::ONE, DaisyPatch::GATE_IN_1); 148 | } 149 | if (IsFull()) { 150 | ad[1].Process(DacHandle::Channel::TWO, DaisyPatch::GATE_IN_2); 151 | } 152 | int env_sel = 0; 153 | for (size_t chn = GetChannelMin(); chn < GetChannelMax(); chn++) { 154 | if(IsFull() && chn > 1) { 155 | env_sel = 1; 156 | } 157 | out[chn][i] = in[chn][i] * ad[env_sel].GetSignal() * 158 | ad[env_sel].GetAmp(); 159 | } 160 | } 161 | } 162 | 163 | void ADDroplet::Draw() { 164 | WriteString(Patch(), 165 | GetScreenMin()+3, 166 | 11, 167 | Font_6x8, 168 | "A: " + 169 | FloatToString(ad[0].GetAttack(), 2) + 170 | "s"); 171 | WriteString(Patch(), 172 | GetScreenMin()+3, 173 | 21, 174 | Font_6x8, 175 | "D: " + 176 | FloatToString(ad[0].GetDecay(), 2) + 177 | "s"); 178 | WriteString(Patch(), 179 | GetScreenMin()+3, 180 | 31, 181 | Font_6x8, 182 | "C: " + 183 | FloatToString(ad[0].GetCurve(), 2)); 184 | WriteString(Patch(), 185 | GetScreenMin()+3, 186 | 41, 187 | Font_6x8, 188 | "Amp: " + 189 | FloatToString(ad[0].GetAmp(), 2)); 190 | 191 | if (!IsFull()) { 192 | if (ad[0].GetMenu()) { 193 | DrawSolidRect(Patch(), GetScreenMin(), 30, GetScreenMin()+1, 49, true); 194 | } else { 195 | DrawSolidRect(Patch(), GetScreenMin(), 10, GetScreenMin()+1, 29, true); 196 | } 197 | } 198 | 199 | if(NeedUpdate()) { 200 | title_graph->Update(); 201 | } 202 | title_graph->SetPixelPercentage(ad[0].GetSignal()); 203 | title_graph->Draw(Patch(), GetScreenMin(), 0); 204 | 205 | 206 | DrawName("AD"); 207 | AnimationInc(); 208 | } 209 | 210 | void ADDroplet::UpdateStateCallback() { 211 | ad[0].Init(Patch(), 212 | sample_rate, 213 | State()); 214 | if (IsFull()) { 215 | ad[1].Init(Patch(), 216 | sample_rate, 217 | State()); 218 | } 219 | if (title_graph != NULL) { 220 | delete title_graph; 221 | } 222 | CreateTitleGraph(); 223 | } 224 | 225 | void ADDroplet::SetControls() {} 226 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef CASCADE_UTIL_H_ 4 | #define CASCADE_UTIL_H_ 5 | 6 | #include "daisysp.h" 7 | #include "daisy_patch.h" 8 | 9 | #include 10 | #include 11 | 12 | using namespace daisy; 13 | using namespace daisysp; 14 | 15 | #define SSD1309_WIDTH 128 16 | 17 | /* 18 | * Draws a solid rectangle on screen. 19 | * 20 | * @param patch daisy patch board 21 | * @param x1 x coordinate of the first point 22 | * @param y1 y coordinate of the first point 23 | * @param x2 x coordinate of the second point 24 | * @param y2 y coordinate of the second point 25 | * @param on draw screen on or off 26 | */ 27 | void DrawSolidRect(DaisyPatch* patch, 28 | uint8_t x1, 29 | uint8_t y1, 30 | uint8_t x2, 31 | uint8_t y2, 32 | bool on); 33 | 34 | /* 35 | * Draws text on screen flushed left. 36 | * 37 | * @param patch daisy patch board 38 | * @param x start of text x coordinate 39 | * @param y start of text y coordinate 40 | * @param font text font 41 | * @param text text to be written 42 | * @param on draw screen on or off 43 | */ 44 | void WriteString(DaisyPatch* patch, 45 | int x, 46 | int y, 47 | FontDef font, 48 | std::string text, 49 | bool on); 50 | 51 | /* 52 | * Draws text on screen flushed left. 53 | * 54 | * @param patch daisy patch board 55 | * @param x start of text x coordinate 56 | * @param y start of text y coordinate 57 | * @param font text font 58 | * @param text text to be written 59 | */ 60 | void WriteString(DaisyPatch* patch, 61 | int x, 62 | int y, 63 | FontDef font, 64 | std::string text); 65 | 66 | /* 67 | * Draws text on screen flushed left. 68 | * 69 | * @param patch daisy patch board 70 | * @param x start of text x coordinate 71 | * @param y start of text y coordinate 72 | * @param text text to be written 73 | * @param on draw screen on or off 74 | */ 75 | void WriteString(DaisyPatch* patch, 76 | int x, 77 | int y, 78 | std::string text, 79 | bool on); 80 | 81 | /* 82 | * Draws text on screen flushed left. 83 | * 84 | * @param patch daisy patch board 85 | * @param x start of text x coordinate 86 | * @param y start of text y coordinate 87 | * @param text text to be written 88 | */ 89 | void WriteString(DaisyPatch* patch, 90 | int x, 91 | int y, 92 | std::string text); 93 | 94 | 95 | /* 96 | * Draws text on screen centered. 97 | * 98 | * @param patch daisy patch board 99 | * @param x center of text x coordinate 100 | * @param y start of text y coordinate 101 | * @param font text font 102 | * @param text text to be written 103 | * @param on draw screen on or off 104 | */ 105 | void WriteCenteredString(DaisyPatch* patch, 106 | int x, 107 | int y, 108 | FontDef font, 109 | std::string text, 110 | bool on); 111 | 112 | /* 113 | * Draws text on screen centered. 114 | * 115 | * @param patch daisy patch board 116 | * @param x center of text x coordinate 117 | * @param y start of text y coordinate 118 | * @param font text font 119 | * @param text text to be written 120 | */ 121 | void WriteCenteredString(DaisyPatch* patch, 122 | int x, 123 | int y, 124 | FontDef font, 125 | std::string text); 126 | 127 | /* 128 | * Draws text on screen centered taking up two lines. 129 | * 130 | * @param patch daisy patch board 131 | * @param x center of text x coordinate 132 | * @param y start of text y coordinate 133 | * @param width text field width 134 | * @param font text font 135 | * @param text text to be written 136 | * @param on draw screen on or off 137 | */ 138 | void WriteDoubleCentered(DaisyPatch* patch, 139 | int x, 140 | int y, 141 | int width, 142 | FontDef font, 143 | std::string text, 144 | bool on); 145 | 146 | /* 147 | * Draws text on screen centered taking up two lines. 148 | * 149 | * @param patch daisy patch board 150 | * @param x center of text x coordinate 151 | * @param y start of text y coordinate 152 | * @param width text field width 153 | * @param font text font 154 | * @param text text to be written 155 | */ 156 | void WriteDoubleCentered(DaisyPatch* patch, 157 | int x, 158 | int y, 159 | int width, 160 | FontDef font, 161 | std::string text); 162 | 163 | /* 164 | * Converts oscilator to name of wave shape. 165 | * 166 | * @param wf wave shape 167 | * @return name of wave shap 168 | */ 169 | std::string WaveToString(uint8_t wf); 170 | 171 | /* 172 | * Converts float to formatted string. 173 | * 174 | * @param input number 175 | * @param number of decimal places 176 | */ 177 | std::string FloatToString(float num, int places); 178 | 179 | /* 180 | * Draws two rectangles on the left and right side of a given area. 181 | * 182 | * @param patch daisy patch board 183 | * @param divider rectangle length 184 | * @param width_min left edge of bounding box 185 | * @param width_max right enge of bounding box 186 | * @param rect_one_min first rectangle lowest edge 187 | * @param rect_one_max first rectangle highest edge 188 | * @param rect_two_min second rectangle lowest edge 189 | * @param rect_two_max second rectangle highest edge 190 | */ 191 | void DrawTwoDividedRectangles(DaisyPatch* patch, 192 | int divider, 193 | int width_min, 194 | int width_max, 195 | int rect_one_min, 196 | int rect_one_max, 197 | int rect_two_min, 198 | int rect_two_max); 199 | 200 | /* 201 | * Draws four rectangles on the left and right side of a given area. 202 | * 203 | * @param patch daisy patch board 204 | * @param divider rectangle length 205 | * @param width_min left edge of bounding box 206 | * @param width_max right enge of bounding box 207 | * @param rect_one_min first rectangle lowest edge 208 | * @param rect_one_max first rectangle highest edge 209 | * @param rect_two_min second rectangle lowest edge 210 | * @param rect_two_max second rectangle highest edge 211 | * @param rect_one_min third rectangle lowest edge 212 | * @param rect_one_max third rectangle highest edge 213 | * @param rect_two_min forth rectangle lowest edge 214 | * @param rect_two_max forth rectangle highest edge 215 | */ 216 | void DrawFourDividedRectangles(DaisyPatch* patch, 217 | int divider, 218 | int width_min, 219 | int width_max, 220 | int rect_one_min, 221 | int rect_one_max, 222 | int rect_two_min, 223 | int rect_two_max, 224 | int rect_three_min, 225 | int rect_three_max, 226 | int rect_four_min, 227 | int rect_four_max); 228 | 229 | /* 230 | * Converts a graphic pixel location to a new one based on 231 | * a given shift. 232 | * 233 | * @param pos pixel position 234 | * @param shift shift amount 235 | * @param array_size max length of dimension 236 | * return new pixel position 237 | */ 238 | int GetShiftArray(int pos, 239 | int shift, 240 | int array_size); 241 | 242 | #endif // CASCADE_UTIL_H_ 243 | --------------------------------------------------------------------------------