├── res ├── screenshot.png ├── screenshot-pianoroll.png ├── noun_notes_1168475.svg ├── PB61303White.svg ├── CKSS_0_White.svg ├── CKSS_1_White.svg └── kisscc0-astronomy-light-darkness-universe-celestial-event-variety-of-musical-notes-silhouette.svg ├── src ├── ModuleDragType.cpp ├── ModuleDragType.hpp ├── SongRoll │ ├── Transport.cppx │ ├── SongRollData.cppx │ ├── Transport.hpp │ ├── RollArea.hpp │ ├── PatternControlWidget.hpp │ ├── ClockDivControlWidget.hpp │ ├── PatternHeaderWidget.hpp │ ├── RepeatControlWidget.hpp │ ├── SongRollData.hpp │ ├── SongRollModule.cppx │ ├── RollArea.cppx │ ├── DragModes.hpp │ ├── PatternControllerSlice.hpp │ ├── SongRollModule.hpp │ ├── SongRollWidget.hpp │ ├── PatternControlWidget.cppx │ ├── PatternHeaderWidget.cppx │ ├── PatternControllerSlice.cppx │ ├── RepeatControlWidget.cppx │ ├── ClockDivControlWidget.cppx │ └── SongRollWidget.cppx ├── Consts.hpp ├── PianoRoll │ ├── MenuItems │ │ ├── CancelPasteItem.hpp │ │ ├── CopyPatternItem.hpp │ │ ├── CopyMeasureItem.hpp │ │ ├── PastePatternItem.hpp │ │ ├── PasteMeasureItem.hpp │ │ ├── ClockBufferItem.hpp │ │ ├── ClearNotesItem.hpp │ │ └── NotesToShowItem.hpp │ ├── Auditioner.hpp │ ├── PianoRollWidget.hpp │ ├── PatternWidget.hpp │ ├── Auditioner.cpp │ ├── Transport.hpp │ ├── PianoRollModule.hpp │ ├── DragModes.hpp │ ├── PatternData.hpp │ ├── RollAreaWidget.hpp │ ├── Transport.cpp │ ├── PianoRollWidget.cpp │ ├── DragModes.cpp │ ├── PatternWidget.cpp │ └── PianoRollModule.cpp ├── ValueChangeTrigger.hpp ├── ModuleTextWidget.hpp ├── PianoRollModule.cpp ├── SongRollModule.cppx ├── ModuleTextWidget.cpp ├── plugin.cpp ├── BaseWidget.hpp ├── plugin.hpp ├── ButtonTest.cppx ├── gverbdsp.h ├── PolyNos.cpp ├── CVMmt.cpp ├── CVTgl.cpp ├── CV5to5.cpp ├── CV0to10.cpp ├── gverbdsp.c ├── DuckModule.cpp ├── CVS0to10.cpp ├── Sync.cpp ├── BaseWidget.cpp ├── SEQAdapter.cpp ├── ladspa-util.h ├── gverb.c ├── gverb.h └── GVerbModule.cpp ├── .gitignore ├── Makefile ├── .vscode └── settings.json ├── plugin.json └── README.md /res/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rcomian/rcm-modules/HEAD/res/screenshot.png -------------------------------------------------------------------------------- /res/screenshot-pianoroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rcomian/rcm-modules/HEAD/res/screenshot-pianoroll.png -------------------------------------------------------------------------------- /src/ModuleDragType.cpp: -------------------------------------------------------------------------------- 1 | #include "ModuleDragType.hpp" 2 | 3 | ModuleDragType::ModuleDragType() {} 4 | 5 | ModuleDragType::~ModuleDragType() {} 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /dist 3 | /plugin.dylib 4 | /plugin.dll 5 | /plugin.so 6 | .DS_Store 7 | dep/libopenmpt-0.3.10* 8 | *.mod 9 | *.xm 10 | .vscode/ipch 11 | -------------------------------------------------------------------------------- /src/ModuleDragType.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | struct ModuleDragType { 4 | ModuleDragType(); 5 | virtual ~ModuleDragType(); 6 | 7 | virtual void onDragMove(const rack::event::DragMove &e) = 0; 8 | }; 9 | -------------------------------------------------------------------------------- /src/SongRoll/Transport.cppx: -------------------------------------------------------------------------------- 1 | #include "../SongRoll/Transport.hpp" 2 | 3 | using namespace SongRoll; 4 | 5 | Transport::Transport(SongRollData* data) : data(data) {} 6 | 7 | void Transport::reset() { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/Consts.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | static const NVGcolor NV_BLACK = nvgRGBAf(0.f, 0.f, 0.f, 1.0); 4 | static const NVGcolor NV_YELLOW = nvgRGBAf(1.f, 0.9f, 0.3f, 1.f); 5 | static const NVGcolor NV_YELLOW_H = nvgRGBAf(1.f, 0.9f, 0.3f, 0.5); 6 | static const float TAU = 6.283185307; 7 | -------------------------------------------------------------------------------- /src/PianoRoll/MenuItems/CancelPasteItem.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | struct CancelPasteItem : MenuItem { 6 | PianoRollWidget *widget = NULL; 7 | void onAction(const event::Action &e) override { 8 | widget->state = COPYREADY; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/SongRoll/SongRollData.cppx: -------------------------------------------------------------------------------- 1 | #include "../SongRoll/SongRollData.hpp" 2 | 3 | using namespace SongRoll; 4 | 5 | Section::Section() { 6 | channels.resize(8); 7 | } 8 | 9 | SongRollData::SongRollData() { 10 | sections.resize(1); 11 | } 12 | 13 | void SongRollData::reset() { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/SongRoll/Transport.hpp: -------------------------------------------------------------------------------- 1 | namespace SongRoll { 2 | 3 | class SongRollData; 4 | 5 | class Transport { 6 | public: 7 | Transport(SongRollData* data); 8 | void reset(); 9 | 10 | int currentSection = 0; 11 | 12 | private: 13 | SongRollData* data; 14 | }; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/SongRoll/RollArea.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | namespace SongRoll { 6 | 7 | class SongRollData; 8 | 9 | class RollArea : public VirtualWidget { 10 | public: 11 | SongRollData& data; 12 | RollArea(Rect box, SongRollData& data); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/ValueChangeTrigger.hpp: -------------------------------------------------------------------------------- 1 | template 2 | struct ValueChangeTrigger { 3 | T value; 4 | bool changed; 5 | 6 | ValueChangeTrigger(T initialValue) : value(initialValue), changed(false) { } 7 | 8 | bool process(T newValue) { 9 | changed = value != newValue; 10 | value = newValue; 11 | return changed; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/PianoRoll/Auditioner.hpp: -------------------------------------------------------------------------------- 1 | class Auditioner { 2 | public: 3 | void start(int step); 4 | void retrigger(); 5 | void stop(); 6 | 7 | bool isAuditioning(); 8 | int stepToAudition(); 9 | bool consumeRetrigger(); 10 | bool consumeStopEvent(); 11 | 12 | private: 13 | int step = -1; 14 | bool needsRetrigger = false; 15 | bool auditioning = false; 16 | bool stopPending = false; 17 | }; 18 | 19 | -------------------------------------------------------------------------------- /src/PianoRoll/MenuItems/CopyPatternItem.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | struct CopyPatternItem : MenuItem { 6 | PianoRollWidget *widget = NULL; 7 | PianoRollModule *module = NULL; 8 | int type; 9 | void onAction(const event::Action &e) override { 10 | module->patternData.copyPattern(module->transport.currentPattern()); 11 | widget->state = PATTERNLOADED; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/SongRoll/PatternControlWidget.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | namespace SongRoll { 6 | 7 | struct SongRollData; 8 | 9 | class PatternControlWidget : public VirtualWidget { 10 | public: 11 | int pattern=0; 12 | 13 | PatternControlWidget(); 14 | 15 | void draw(NVGcontext* ctx) override; 16 | void onMouseDown(EventMouseDown& e) override; 17 | }; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/SongRoll/ClockDivControlWidget.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | namespace SongRoll { 6 | 7 | struct SongRollData; 8 | 9 | class ClockDivControlWidget : public VirtualWidget { 10 | public: 11 | int clock_div=1; 12 | 13 | ClockDivControlWidget(); 14 | 15 | void draw(NVGcontext* ctx) override; 16 | void onMouseDown(EventMouseDown& e) override; 17 | }; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/ModuleTextWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct TextFieldModule { 4 | std::string text; 5 | bool dirty; 6 | 7 | json_t* dataToJson(); 8 | void dataFromJson(json_t* rootJ); 9 | }; 10 | 11 | struct TextFieldWidget : LedDisplayTextField { 12 | TextFieldModule *module = nullptr; 13 | 14 | void step() override; 15 | void onChange(const ChangeEvent& e) override; 16 | void setModule(TextFieldModule *module); 17 | }; 18 | -------------------------------------------------------------------------------- /src/PianoRoll/MenuItems/CopyMeasureItem.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | struct CopyMeasureItem : MenuItem { 6 | PianoRollWidget *widget = NULL; 7 | PianoRollModule *module = NULL; 8 | void onAction(const event::Action &e) override { 9 | module->patternData.copyMeasure(module->transport.currentPattern(), widget->rollAreaWidget->state.currentMeasure); 10 | widget->state = MEASURELOADED; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/SongRoll/PatternHeaderWidget.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | namespace SongRoll { 6 | 7 | class PatternHeaderWidget : public VirtualWidget { 8 | public: 9 | int repeats; 10 | int repeats_completed; 11 | int pattern; 12 | bool active; 13 | 14 | PatternHeaderWidget(int repeats, int repeats_completed, int pattern); 15 | 16 | void draw(NVGcontext* ctx) override; 17 | }; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/SongRoll/RepeatControlWidget.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | namespace SongRoll { 6 | 7 | struct SongRollData; 8 | 9 | class RepeatControlWidget : public VirtualWidget { 10 | public: 11 | int repeats=1; 12 | int repeats_complete=0; 13 | int repeat_mode=1; 14 | 15 | RepeatControlWidget(); 16 | 17 | void draw(NVGcontext* ctx) override; 18 | void onMouseDown(EventMouseDown& e) override; 19 | }; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/PianoRollModule.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "PianoRoll/PianoRollModule.hpp" 4 | #include "PianoRoll/PianoRollWidget.hpp" 5 | 6 | // Specify the Module and ModuleWidget subclass, human-readable 7 | // author name for categorization per plugin, module slug (should never 8 | // change), human-readable module name, and any number of tags 9 | // (found in `include/tags.hpp`) separated by commas. 10 | Model *modelPianoRollModule = createModel("rcm-pianoroll"); 11 | -------------------------------------------------------------------------------- /src/PianoRoll/MenuItems/PastePatternItem.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | struct PastePatternItem : MenuItem { 6 | PianoRollWidget *widget = NULL; 7 | PianoRollModule *module = NULL; 8 | void onAction(const event::Action &e) override { 9 | APP->history->push(new PatternData::PatternAction("paste pattern", module->patternData.moduleId, module->transport.currentPattern(), module->patternData)); 10 | module->patternData.pastePattern(module->transport.currentPattern()); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/SongRollModule.cppx: -------------------------------------------------------------------------------- 1 | #include "GVerbWidget.hpp" 2 | 3 | #include "SongRoll/SongRollModule.hpp" 4 | #include "SongRoll/SongRollWidget.hpp" 5 | 6 | using namespace SongRoll; 7 | 8 | // Specify the Module and ModuleWidget subclass, human-readable 9 | // author name for categorization per plugin, module slug (should never 10 | // change), human-readable module name, and any number of tags 11 | // (found in `include/tags.hpp`) separated by commas. 12 | Model *modelSongRollModule = createModel("rcm-songroll"); 13 | -------------------------------------------------------------------------------- /src/PianoRoll/MenuItems/PasteMeasureItem.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | struct PasteMeasureItem : MenuItem { 6 | PianoRollWidget *widget = NULL; 7 | PianoRollModule *module = NULL; 8 | void onAction(const event::Action &e) override { 9 | APP->history->push(new PatternData::PatternAction("paste measure", module->patternData.moduleId, module->transport.currentPattern(), module->patternData)); 10 | module->patternData.pasteMeasure(module->transport.currentPattern(), widget->rollAreaWidget->state.currentMeasure); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/PianoRoll/MenuItems/ClockBufferItem.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | struct ClockBufferItem : MenuItem { 6 | char buffer[100]; 7 | PianoRollModule* module; 8 | int value; 9 | ClockBufferItem(PianoRollModule* module, int value) { 10 | this->module = module; 11 | this->value = value; 12 | 13 | snprintf(buffer, 10, "%d", value); 14 | text = buffer; 15 | if (value == module->clockDelay) { 16 | rightText = "✓"; 17 | } 18 | } 19 | void onAction(const event::Action &e) override { 20 | module->clockDelay = value; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/PianoRoll/MenuItems/ClearNotesItem.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | struct ClearNotesItem : MenuItem { 6 | PianoRollModule *module = NULL; 7 | 8 | ClearNotesItem(PianoRollModule* module) { 9 | this->module = module; 10 | text = "Clear Notes"; 11 | } 12 | 13 | void onAction(const event::Action &e) override { 14 | APP->history->push(new PatternData::PatternAction("clear notes", module->patternData.moduleId, module->transport.currentPattern(), module->patternData)); 15 | module->patternData.clearPatternSteps(module->transport.currentPattern()); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /src/SongRoll/SongRollData.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace SongRoll { 4 | 5 | 6 | class ChannelConfig { 7 | public: 8 | enum eRepeatMode { 9 | FREE, 10 | REPEATS, 11 | LIMIT 12 | }; 13 | 14 | int pattern = 1; 15 | int repeats = 1; 16 | eRepeatMode repeat_mode = eRepeatMode::FREE; 17 | int clock_div = 1; 18 | }; 19 | 20 | class Section { 21 | public: 22 | std::vector channels; 23 | Section(); 24 | }; 25 | 26 | class SongRollData { 27 | public: 28 | std::vector
sections; 29 | SongRollData(); 30 | void reset(); 31 | }; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/PianoRoll/MenuItems/NotesToShowItem.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | struct NotesToShowItem : MenuItem { 6 | char buffer[100]; 7 | PianoRollWidget* module; 8 | int value; 9 | NotesToShowItem(PianoRollWidget* module, int value) { 10 | this->module = module; 11 | this->value = value; 12 | 13 | snprintf(buffer, 10, "%d", value); 14 | text = buffer; 15 | if (value == module->rollAreaWidget->state.notesToShow) { 16 | rightText = "✓"; 17 | } 18 | } 19 | void onAction(const event::Action &e) override { 20 | module->rollAreaWidget->state.notesToShow = value; 21 | module->rollAreaWidget->state.dirty = true; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/PianoRoll/PianoRollWidget.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "rack.hpp" 5 | #include "../BaseWidget.hpp" 6 | #include "RollAreaWidget.hpp" 7 | 8 | using namespace rack; 9 | 10 | struct PianoRollModule; 11 | struct PianoRollWidget; 12 | struct ModuleDragType; 13 | 14 | enum CopyPasteState { 15 | COPYREADY, 16 | PATTERNLOADED, 17 | MEASURELOADED 18 | }; 19 | 20 | struct PianoRollWidget : BaseWidget { 21 | PianoRollModule* module; 22 | CopyPasteState state; 23 | RollAreaWidget* rollAreaWidget; 24 | 25 | PianoRollWidget(PianoRollModule *module); 26 | 27 | Rect getRollArea(); 28 | 29 | // Event Handlers 30 | void step() override; 31 | void appendContextMenu(Menu* menu) override; 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /src/SongRoll/SongRollModule.cppx: -------------------------------------------------------------------------------- 1 | #include "../SongRoll/SongRollModule.hpp" 2 | 3 | using namespace rack; 4 | using namespace SongRoll; 5 | 6 | SongRollModule::SongRollModule() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS), runInputActive(false), transport(&songRollData) { 7 | } 8 | 9 | void SongRollModule::onReset() { 10 | transport.reset(); 11 | songRollData.reset(); 12 | } 13 | 14 | json_t *SongRollModule::dataToJson() { 15 | json_t *rootJ = Module::dataToJson(); 16 | if (rootJ == NULL) { 17 | rootJ = json_object(); 18 | } 19 | 20 | return rootJ; 21 | } 22 | 23 | void SongRollModule::dataFromJson(json_t *rootJ) { 24 | Module::dataFromJson(rootJ); 25 | 26 | } 27 | 28 | void SongRollModule::step() { 29 | } 30 | -------------------------------------------------------------------------------- /src/SongRoll/RollArea.cppx: -------------------------------------------------------------------------------- 1 | #include "../SongRoll/RollArea.hpp" 2 | #include "../SongRoll/SongRollData.hpp" 3 | #include "../SongRoll/PatternControllerSlice.hpp" 4 | 5 | namespace SongRoll { 6 | 7 | RollArea::RollArea(Rect box, SongRollData& data) : data(data) { 8 | this->box = box; 9 | const int numChannels = (int)data.sections[0].channels.size(); 10 | const float channelWidth = box.size.x / data.sections[0].channels.size(); 11 | for (int i = 0; i < numChannels; i++) { 12 | auto *slice = new PatternControllerSlice(i, data, 0); 13 | slice->box.pos.x = (channelWidth * i); 14 | slice->box.pos.y = 0; 15 | slice->box.size.x = channelWidth; 16 | slice->box.size.y = box.size.y; 17 | 18 | addChild(slice); 19 | } 20 | } 21 | 22 | 23 | } -------------------------------------------------------------------------------- /src/PianoRoll/PatternWidget.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | struct PianoRollWidget; 6 | struct PianoRollModule; 7 | 8 | struct PatternWidget : LedDisplay { 9 | /** Not owned */ 10 | PianoRollWidget *widget = NULL; 11 | PianoRollModule *module = NULL; 12 | 13 | LedDisplayChoice *patternChoice; 14 | LedDisplaySeparator *patternSeparator; 15 | LedDisplayChoice *octaveChoice; 16 | LedDisplaySeparator *octaveSeparator; 17 | LedDisplayChoice *measuresChoice; 18 | LedDisplaySeparator *measuresSeparator; 19 | LedDisplayChoice *beatsPerMeasureChoice; 20 | LedDisplaySeparator *beatsPerMeasureSeparator; 21 | LedDisplayChoice *divisionsPerBeatChoice; 22 | LedDisplaySeparator *divisionsPerBeatSeparator; 23 | LedDisplayChoice *sequenceRunningChoice; 24 | PatternWidget(); 25 | }; 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # If RACK_DIR is not defined when calling the Makefile, default to two directories above 2 | RACK_DIR ?= ../.. 3 | 4 | # FLAGS will be passed to both the C and C++ compiler 5 | #FLAGS += -Idep/openmpt-libopenmpt-0.3.10/soundlib -Idep/openmpt-libopenmpt-0.3.10/common 6 | CFLAGS += 7 | CXXFLAGS += 8 | 9 | # Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path. 10 | # Static libraries are fine. 11 | #LDFLAGS = -Ldep -Ldep/openmpt-libopenmpt-0.3.10/bin/libopenmpt.a 12 | #LDFLAGS = -Ldep 13 | 14 | # Add .cpp and .c files to the build 15 | SOURCES += $(wildcard src/*.cpp) $(wildcard src/**/*.cpp) $(wildcard src/*.c) 16 | 17 | # Add files to the ZIP package when running `make dist` 18 | # The compiled plugin is automatically added. 19 | DISTRIBUTABLES += $(wildcard LICENSE*) res 20 | 21 | # Include the VCV Rack plugin Makefile framework 22 | include $(RACK_DIR)/plugin.mk 23 | -------------------------------------------------------------------------------- /src/ModuleTextWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "BaseWidget.hpp" 3 | #include "ModuleTextWidget.hpp" 4 | 5 | json_t* TextFieldModule::dataToJson() { 6 | json_t* rootJ = json_object(); 7 | json_object_set_new(rootJ, "text", json_stringn(text.c_str(), text.size())); 8 | return rootJ; 9 | } 10 | 11 | void TextFieldModule::dataFromJson(json_t* rootJ) { 12 | json_t* textJ = json_object_get(rootJ, "text"); 13 | if (textJ) 14 | text = json_string_value(textJ); 15 | dirty = true; 16 | } 17 | 18 | void TextFieldWidget::step() { 19 | TextField::step(); 20 | if (module && module->dirty) { 21 | setText(module->text); 22 | module->dirty = false; 23 | } 24 | } 25 | 26 | void TextFieldWidget::onChange(const ChangeEvent& e) { 27 | if (module) 28 | module->text = text; 29 | } 30 | 31 | void TextFieldWidget::setModule(TextFieldModule *pmodule) { 32 | module = pmodule; 33 | 34 | if (module) { 35 | setText(module->text); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/SongRoll/DragModes.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | using namespace rack; 4 | 5 | namespace SongRoll { 6 | 7 | struct SongRollModule; 8 | struct SongRollWidget; 9 | 10 | struct ModuleDragType { 11 | SongRollWidget* widget; 12 | SongRollModule* module; 13 | 14 | ModuleDragType(SongRollWidget* widget, SongRollModule* module); 15 | virtual ~ModuleDragType(); 16 | 17 | virtual void onDragMove(EventDragMove& e) = 0; 18 | }; 19 | 20 | struct StandardModuleDragging : public ModuleDragType { 21 | StandardModuleDragging(SongRollWidget* widget, SongRollModule* module); 22 | virtual ~StandardModuleDragging(); 23 | 24 | void onDragMove(EventDragMove& e) override; 25 | }; 26 | 27 | struct ColourDragging : public ModuleDragType { 28 | ColourDragging(SongRollWidget* widget, SongRollModule* module); 29 | virtual ~ColourDragging(); 30 | 31 | void onDragMove(EventDragMove& e) override; 32 | }; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/SongRoll/PatternControllerSlice.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | using namespace rack; 3 | 4 | namespace SongRoll { 5 | 6 | struct SongRollData; 7 | struct PatternHeaderWidget; 8 | struct PatternControlWidget; 9 | struct RepeatControlWidget; 10 | struct ClockDivControlWidget; 11 | 12 | class PatternControllerSlice : public VirtualWidget { 13 | public: 14 | const int channel; 15 | SongRollData& data; 16 | SequentialLayout* layout; 17 | PatternHeaderWidget* header; 18 | PatternControlWidget* pattern; 19 | RepeatControlWidget* repeats; 20 | ClockDivControlWidget* clock_div; 21 | 22 | PatternControllerSlice(int channel, SongRollData& data, int section); 23 | void draw(NVGcontext* ctx) override; 24 | void step() override; 25 | void onMouseDown(EventMouseDown& e) override; 26 | void setSection(int section); 27 | 28 | private: 29 | int section = 0; 30 | bool sectionChanged = true; 31 | }; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/PianoRoll/Auditioner.cpp: -------------------------------------------------------------------------------- 1 | #include "../PianoRoll/Auditioner.hpp" 2 | 3 | 4 | void Auditioner::start(int step) { 5 | if (this->step != step || !auditioning) { 6 | needsRetrigger = true; 7 | } 8 | 9 | this->step = step; 10 | auditioning = true; 11 | } 12 | 13 | void Auditioner::retrigger() { 14 | needsRetrigger = true; 15 | } 16 | 17 | void Auditioner::stop() { 18 | auditioning = false; 19 | needsRetrigger = false; 20 | stopPending = true; 21 | } 22 | 23 | bool Auditioner::isAuditioning() { 24 | return auditioning; 25 | } 26 | 27 | int Auditioner::stepToAudition() { 28 | return step; 29 | } 30 | 31 | bool Auditioner::consumeRetrigger() { 32 | if (needsRetrigger) { 33 | needsRetrigger = false; 34 | return true; 35 | } else { 36 | return false; 37 | } 38 | } 39 | 40 | bool Auditioner::consumeStopEvent() { 41 | if (stopPending) { 42 | stopPending = false; 43 | return true; 44 | } else { 45 | return false; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /res/noun_notes_1168475.svg: -------------------------------------------------------------------------------- 1 | Created by abdul karimfrom the Noun Project -------------------------------------------------------------------------------- /src/plugin.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include 4 | 5 | #if ARCH_WIN 6 | #include 7 | #include 8 | #define mkdir(_dir, _perms) _mkdir(_dir) 9 | #else 10 | #include 11 | #endif 12 | 13 | using namespace std; 14 | 15 | Plugin *pluginInstance; 16 | 17 | void init(Plugin *p) { 18 | pluginInstance = p; 19 | 20 | // Add all Models defined throughout the plugin 21 | p->addModel(modelGVerbModule); 22 | p->addModel(modelCV0to10Module); 23 | p->addModel(modelCVS0to10Module); 24 | p->addModel(modelCV5to5Module); 25 | p->addModel(modelCVMmtModule); 26 | p->addModel(modelCVTglModule); 27 | p->addModel(modelPianoRollModule); 28 | p->addModel(modelDuckModule); 29 | p->addModel(modelSEQAdapterModule); 30 | p->addModel(modelSyncModule); 31 | p->addModel(modelPolyNosModule); 32 | // Any other plugin initialization may go here. 33 | // As an alternative, consider lazy-loading assets and lookup tables when your module is created to reduce startup times of Rack. 34 | } 35 | -------------------------------------------------------------------------------- /src/SongRoll/SongRollModule.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | #include "SongRollData.hpp" 4 | #include "Transport.hpp" 5 | 6 | #include "../ValueChangeTrigger.hpp" 7 | 8 | namespace SongRoll { 9 | 10 | struct SongRollModule : rack::Module { 11 | enum ParamIds { 12 | NUM_PARAMS 13 | }; 14 | enum InputIds { 15 | NUM_INPUTS 16 | }; 17 | enum OutputIds { 18 | NUM_OUTPUTS 19 | }; 20 | enum LightIds { 21 | NUM_LIGHTS 22 | }; 23 | 24 | rack::SchmittTrigger clockInputTrigger; 25 | rack::SchmittTrigger resetInputTrigger; 26 | rack::SchmittTrigger runInputTrigger; 27 | 28 | ValueChangeTrigger runInputActive; 29 | rack::RingBuffer clockBuffer; 30 | int clockDelay = 0; 31 | 32 | SongRollData songRollData; 33 | Transport transport; 34 | 35 | SongRollModule(); 36 | 37 | void step() override; 38 | void onReset() override; 39 | 40 | json_t *dataToJson() override; 41 | void dataFromJson(json_t *rootJ) override; 42 | }; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/SongRoll/SongRollWidget.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "rack.hpp" 5 | #include "../BaseWidget.hpp" 6 | 7 | using namespace rack; 8 | 9 | 10 | namespace SongRoll { 11 | 12 | struct ModuleDragType; 13 | struct SongRollModule; 14 | 15 | struct SongRollWidget : BaseWidget { 16 | SongRollModule* module; 17 | 18 | SongRollWidget(SongRollModule *module); 19 | 20 | Rect getRollArea(); 21 | 22 | void drawBackgroundColour(NVGcontext* ctx); 23 | void drawPatternEditors(NVGcontext* ctx); 24 | 25 | // Event Handlers 26 | 27 | void appendContextMenu(Menu* menu) override; 28 | void draw(NVGcontext* ctx) override; 29 | void onMouseDown(EventMouseDown& e) override; 30 | void onDragStart(EventDragStart& e) override; 31 | void baseDragMove(EventDragMove& e); 32 | void onDragMove(EventDragMove& e) override; 33 | void onDragEnd(EventDragEnd& e) override; 34 | 35 | json_t *dataToJson() override; 36 | void dataFromJson(json_t *rootJ) override; 37 | 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/PianoRoll/Transport.hpp: -------------------------------------------------------------------------------- 1 | class PatternData; 2 | 3 | class Transport { 4 | public: 5 | Transport(PatternData* data); 6 | 7 | int currentPattern(); 8 | int currentMeasure(); 9 | int currentStepInMeasure(); 10 | int currentStepInPattern(); 11 | bool isLastStepOfPattern(); 12 | 13 | void setPattern(int pattern); 14 | void setMeasure(int measure); 15 | void setStepInMeasure(int step); 16 | void setStepInPattern(int step); 17 | 18 | void advancePattern(int offset); 19 | void advanceStep(); 20 | 21 | void lockMeasure(); 22 | void unlockMeasure(); 23 | bool isLocked(); 24 | 25 | void toggleRun(); 26 | void setRun(bool running); 27 | bool isRunning(); 28 | 29 | void toggleRecording(); 30 | bool isRecording(); 31 | bool isPendingRecording(); 32 | 33 | void reset(); 34 | 35 | bool dirty = true; 36 | bool consumeDirty(); 37 | 38 | private: 39 | int pattern = 0; 40 | int stepInPattern = -1; 41 | 42 | bool locked = false; 43 | bool running = true; 44 | bool recording = false; 45 | bool pendingRecording = false; 46 | 47 | PatternData* patternData; 48 | }; 49 | -------------------------------------------------------------------------------- /src/BaseWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "rack.hpp" 4 | 5 | using namespace rack; 6 | 7 | struct ModuleDragType; 8 | 9 | struct ColourData { 10 | float backgroundHue = 1.f; 11 | float backgroundSaturation = 1.f; 12 | float backgroundLuminosity = 0.25f; 13 | bool dirty = false; 14 | }; 15 | 16 | struct ColourChangeWidget : Widget { 17 | bool dragging = false; 18 | ColourData *colourData = NULL; 19 | 20 | void onButton(const event::Button &e) override; 21 | void onDragStart(const event::DragStart& e) override; 22 | void onDragMove(const event::DragMove& e) override; 23 | void onDragEnd(const event::DragEnd& e) override; 24 | }; 25 | 26 | struct BaseModule : Module { 27 | ColourData colourData; 28 | 29 | json_t *dataToJson() override; 30 | void dataFromJson(json_t *root) override; 31 | }; 32 | 33 | struct BaseWidget : ModuleWidget { 34 | ColourData colourData; 35 | ColourData *moduleColourData = nullptr; 36 | 37 | BaseWidget(); 38 | 39 | void initColourChange(Rect hotspot, BaseModule* baseModule, float hue, float saturation, float value); 40 | 41 | void step() override; 42 | void draw(const DrawArgs& args) override; 43 | }; 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/plugin.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace rack; 8 | 9 | // notes by abdul karim from the Noun Project 10 | 11 | // Forward-declare the Plugin, defined in Template.cpp 12 | extern Plugin *pluginInstance; 13 | 14 | // Forward-declare each Model, defined in each module source file 15 | extern Model *modelGVerbModule; 16 | extern Model *modelDuckModule; 17 | extern Model *modelCV0to10Module; 18 | extern Model *modelCVS0to10Module; 19 | extern Model *modelCV5to5Module; 20 | extern Model *modelCVMmtModule; 21 | extern Model *modelCVTglModule; 22 | extern Model *modelPianoRollModule; 23 | extern Model *modelSongRollModule; 24 | extern Model *modelButtonTest; 25 | extern Model *modelSEQAdapterModule; 26 | extern Model *modelSyncModule; 27 | extern Model *modelPolyNosModule; 28 | extern Model *modelButtonTest; 29 | 30 | template 31 | std::string stringf( const std::string& format, Args ... args ) 32 | { 33 | size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0' 34 | std::unique_ptr buf( new char[ size ] ); 35 | snprintf( buf.get(), size, format.c_str(), args ... ); 36 | return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside 37 | } 38 | -------------------------------------------------------------------------------- /src/ButtonTest.cppx: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "BaseWidget.hpp" 3 | 4 | using namespace std; 5 | using namespace rack; 6 | 7 | 8 | struct ButtonTest : BaseModule { 9 | enum ParamIds { 10 | SELECT_PRESET, 11 | NUM_PARAMS 12 | }; 13 | enum InputIds { 14 | NUM_INPUTS 15 | }; 16 | enum OutputIds { 17 | NUM_OUTPUTS 18 | }; 19 | enum LightIds { 20 | NUM_LIGHTS 21 | }; 22 | 23 | 24 | 25 | ButtonTest() : BaseModule() { 26 | } 27 | void step() override; 28 | 29 | int preset_num; 30 | 31 | json_t *dataToJson() override 32 | { 33 | return BaseModule::dataToJson(); 34 | } 35 | void dataFromJson(json_t *root) override 36 | { 37 | BaseModule::dataFromJson(root); 38 | } 39 | 40 | // For more advanced Module features, read Rack's engine.hpp header file 41 | // - dataToJson, dataFromJson: serialization of internal data 42 | // - onSampleRateChange: event triggered by a change of sample rate 43 | // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu 44 | }; 45 | 46 | /////////step ////////////////////// 47 | void ButtonTest::step() 48 | { 49 | 50 | }; 51 | 52 | struct ButtonTestWidget : BaseWidget 53 | { 54 | TextField *textField; 55 | ButtonTestWidget(Module *module) : BaseWidget() 56 | { 57 | setModule(module); 58 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/CV0to10.svg"))); 59 | } 60 | }; 61 | 62 | 63 | // Specify the Module and ModuleWidget subclass, human-readable 64 | // author name for categorization per plugin, module slug (should never 65 | // change), human-readable module name, and any number of tags 66 | // (found in `include/tags.hpp`) separated by commas. 67 | Model *modelButtonTest = createModel("rcm-ButtonTest"); -------------------------------------------------------------------------------- /src/SongRoll/PatternControlWidget.cppx: -------------------------------------------------------------------------------- 1 | #include "../SongRoll/PatternControlWidget.hpp" 2 | #include "../Consts.hpp" 3 | 4 | namespace SongRoll { 5 | 6 | PatternControlWidget::PatternControlWidget() {} //int pattern) : pattern(pattern) {} 7 | 8 | void PatternControlWidget::draw(NVGcontext* ctx) { 9 | float y = 1; 10 | 11 | nvgBeginPath(ctx); 12 | nvgFontSize(ctx, 10); 13 | nvgTextAlign(ctx, NVG_ALIGN_CENTER + NVG_ALIGN_TOP); 14 | nvgFillColor(ctx, NV_YELLOW); 15 | nvgText(ctx, box.size.x * 0.5, y, "-- Pattern --", NULL); 16 | 17 | y += 10; 18 | 19 | nvgBeginPath(ctx); 20 | nvgFontSize(ctx, 14); 21 | nvgTextAlign(ctx, NVG_ALIGN_CENTER + NVG_ALIGN_TOP); 22 | nvgFillColor(ctx, NV_YELLOW); 23 | nvgText(ctx, box.size.x * 0.25, y, "−", NULL); 24 | 25 | nvgBeginPath(ctx); 26 | nvgFontSize(ctx, 14); 27 | nvgTextAlign(ctx, NVG_ALIGN_CENTER + NVG_ALIGN_TOP); 28 | nvgFillColor(ctx, NV_YELLOW); 29 | nvgText(ctx, box.size.x * 0.75, y, "+", NULL); 30 | 31 | char patternBuffer[100]; 32 | sprintf(patternBuffer, "%d", pattern + 1); 33 | 34 | nvgBeginPath(ctx); 35 | nvgFontSize(ctx, 14); 36 | nvgTextAlign(ctx, NVG_ALIGN_CENTER + NVG_ALIGN_TOP); 37 | nvgFillColor(ctx, NV_YELLOW); 38 | nvgText(ctx, box.size.x * 0.5, y, patternBuffer, NULL); 39 | 40 | y += 14; 41 | 42 | box.size.y = y; 43 | } 44 | 45 | void PatternControlWidget::onMouseDown(EventMouseDown& e) { 46 | Rect minus(Vec(0,10), Vec(box.size.x*0.40,14)); 47 | Rect plus(Vec(box.size.x*0.60,10), Vec(box.size.x*0.40,14)); 48 | 49 | if (minus.isContaining(e.pos)) { 50 | pattern = clamp(pattern - 1, 0, 63); 51 | } else if (plus.isContaining(e.pos)) { 52 | pattern = clamp(pattern + 1, 0, 63); 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/gverbdsp.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef GVERBDSP_H 3 | #define GVERBDSP_H 4 | 5 | #include "ladspa-util.h" 6 | 7 | typedef struct { 8 | int size; 9 | int idx; 10 | float *buf; 11 | } ty_fixeddelay; 12 | 13 | typedef struct { 14 | int size; 15 | float coeff; 16 | int idx; 17 | float *buf; 18 | } ty_diffuser; 19 | 20 | typedef struct { 21 | float damping; 22 | float delay; 23 | } ty_damper; 24 | 25 | ty_diffuser *diffuser_make(int, float); 26 | void diffuser_free(ty_diffuser *); 27 | void diffuser_flush(ty_diffuser *); 28 | //float diffuser_do(ty_diffuser *, float); 29 | 30 | ty_damper *damper_make(float); 31 | void damper_free(ty_damper *); 32 | void damper_flush(ty_damper *); 33 | //void damper_set(ty_damper *, float); 34 | //float damper_do(ty_damper *, float); 35 | 36 | ty_fixeddelay *fixeddelay_make(int); 37 | void fixeddelay_free(ty_fixeddelay *); 38 | void fixeddelay_flush(ty_fixeddelay *); 39 | //float fixeddelay_read(ty_fixeddelay *, int); 40 | //void fixeddelay_write(ty_fixeddelay *, float); 41 | 42 | int isprime(int); 43 | int nearest_prime(int, float); 44 | 45 | static __inline float diffuser_do(ty_diffuser *p, float x) 46 | { 47 | float y,w; 48 | 49 | w = x - p->buf[p->idx]*p->coeff; 50 | w = flush_to_zero(w); 51 | y = p->buf[p->idx] + w*p->coeff; 52 | p->buf[p->idx] = w; 53 | p->idx = (p->idx + 1) % p->size; 54 | return(y); 55 | } 56 | 57 | static __inline float fixeddelay_read(ty_fixeddelay *p, int n) 58 | { 59 | int i; 60 | 61 | i = (p->idx - n + p->size) % p->size; 62 | return(p->buf[i]); 63 | } 64 | 65 | static __inline void fixeddelay_write(ty_fixeddelay *p, float x) 66 | { 67 | p->buf[p->idx] = x; 68 | p->idx = (p->idx + 1) % p->size; 69 | } 70 | 71 | static __inline void damper_set(ty_damper *p, float damping) 72 | { 73 | p->damping = damping; 74 | } 75 | 76 | static __inline float damper_do(ty_damper *p, float x) 77 | { 78 | float y; 79 | 80 | y = x*(1.0-p->damping) + p->delay*p->damping; 81 | p->delay = y; 82 | return(y); 83 | } 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /src/PianoRoll/PianoRollModule.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | 3 | #include "../BaseWidget.hpp" 4 | #include "PatternData.hpp" 5 | #include "Transport.hpp" 6 | #include "Auditioner.hpp" 7 | 8 | #include "../ValueChangeTrigger.hpp" 9 | 10 | #include "RollAreaWidget.hpp" 11 | 12 | struct PianoRollModule : BaseModule { 13 | enum ParamIds { 14 | RUN_BUTTON_PARAM, 15 | RESET_BUTTON_PARAM, 16 | NUM_PARAMS 17 | }; 18 | enum InputIds { 19 | CLOCK_INPUT, 20 | RUN_INPUT, 21 | RESET_INPUT, 22 | PATTERN_INPUT, 23 | RECORD_INPUT, 24 | VOCT_INPUT, 25 | GATE_INPUT, 26 | RETRIGGER_INPUT, 27 | VELOCITY_INPUT, 28 | NUM_INPUTS 29 | }; 30 | enum OutputIds { 31 | CLOCK_OUTPUT, 32 | RUN_OUTPUT, 33 | RESET_OUTPUT, 34 | PATTERN_OUTPUT, 35 | RECORD_OUTPUT, 36 | VOCT_OUTPUT, 37 | GATE_OUTPUT, 38 | RETRIGGER_OUTPUT, 39 | VELOCITY_OUTPUT, 40 | END_OF_PATTERN_OUTPUT, 41 | NUM_OUTPUTS 42 | }; 43 | enum LightIds { 44 | NUM_LIGHTS 45 | }; 46 | 47 | rack::dsp::SchmittTrigger clockInputTrigger; 48 | rack::dsp::SchmittTrigger resetInputTrigger; 49 | rack::dsp::SchmittTrigger runInputTrigger; 50 | 51 | rack::dsp::PulseGenerator retriggerOutputPulse; 52 | rack::dsp::PulseGenerator eopOutputPulse; 53 | rack::dsp::PulseGenerator gateOutputPulse; 54 | 55 | rack::dsp::ClockDivider processDivider; 56 | 57 | Auditioner auditioner; 58 | 59 | ValueChangeTrigger runInputActive; 60 | rack::dsp::RingBuffer clockBuffer; 61 | int clockDelay = 0; 62 | 63 | rack::dsp::SchmittTrigger recordingIn; 64 | rack::dsp::RingBuffer voctInBuffer; 65 | rack::dsp::RingBuffer gateInBuffer; 66 | rack::dsp::RingBuffer retriggerInBuffer; 67 | rack::dsp::RingBuffer velocityInBuffer; 68 | 69 | PatternData patternData; 70 | Transport transport; 71 | 72 | PianoRollModule(); 73 | 74 | void process(const ProcessArgs &args) override; 75 | void onReset() override; 76 | void onAdd() override; 77 | 78 | WidgetState state; 79 | bool driverMode = false; 80 | 81 | json_t *dataToJson() override; 82 | void dataFromJson(json_t *rootJ) override; 83 | }; 84 | -------------------------------------------------------------------------------- /src/SongRoll/PatternHeaderWidget.cppx: -------------------------------------------------------------------------------- 1 | #include "../SongRoll/PatternHeaderWidget.hpp" 2 | #include "../Consts.hpp" 3 | 4 | namespace SongRoll { 5 | PatternHeaderWidget::PatternHeaderWidget(int repeats, int repeats_completed, int pattern) : repeats(repeats), repeats_completed(repeats_completed), pattern(pattern), active(true) {} 6 | 7 | void drawSegmentedCircle(NVGcontext* ctx, float bottomGapRadians, int segmentCount, int litSegments, float strokeWidth, NVGcolor litColour, NVGcolor unlitColour, float x, float y, float radius) { 8 | float arcLength = TAU - bottomGapRadians; 9 | float arcSegmentLength = arcLength / segmentCount; 10 | float arcStart = (TAU * 0.25) + (bottomGapRadians / 2); 11 | float segmentGap = arcSegmentLength * (segmentCount > 16 ? 0.4 : segmentCount > 8 ? 0.2 : 0.1); 12 | for (float j = 0; j < segmentCount; j++) { 13 | nvgBeginPath(ctx); 14 | 15 | nvgStrokeWidth(ctx, strokeWidth); 16 | if (j < litSegments) { 17 | nvgStrokeColor(ctx, litColour); 18 | } else { 19 | nvgStrokeColor(ctx, unlitColour); 20 | } 21 | 22 | float segmentStart = arcStart + j * arcSegmentLength; 23 | float segmentEnd = arcStart + ((j+1) * arcSegmentLength); 24 | 25 | if (j != 0) { 26 | segmentStart += segmentGap / 2; 27 | } 28 | if (j != segmentCount-1) { 29 | segmentEnd -= segmentGap / 2; 30 | } 31 | nvgArc(ctx, x, y, radius, segmentStart, segmentEnd, NVG_CW); 32 | 33 | nvgStroke(ctx); 34 | } 35 | } 36 | 37 | void PatternHeaderWidget::draw(NVGcontext* ctx) { 38 | box.size.y = box.size.x * 0.7; 39 | 40 | Widget::draw(ctx); 41 | 42 | drawSegmentedCircle(ctx, TAU * 0.3, active ? repeats : 1, active ? repeats_completed : 0, 6.f, NV_YELLOW, NV_YELLOW_H, box.size.x * 0.5, box.size.x * 0.5, box.size.x * 0.5 * 0.7); 43 | 44 | char patternNumberBuffer[10]; 45 | if (active) { 46 | sprintf(patternNumberBuffer, "%02d", pattern + 1); 47 | } else { 48 | sprintf(patternNumberBuffer, "−−"); 49 | } 50 | nvgBeginPath(ctx); 51 | nvgFontSize(ctx, 24); 52 | nvgTextAlign(ctx, NVG_ALIGN_CENTER + NVG_ALIGN_MIDDLE); 53 | nvgFillColor(ctx, NV_YELLOW); 54 | nvgText(ctx, box.size.x * 0.5, box.size.x * 0.5, patternNumberBuffer, NULL); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/PolyNos.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "BaseWidget.hpp" 3 | 4 | 5 | 6 | struct PolyNosModule : BaseModule { 7 | enum ParamIds { 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | SRC_INPUT, 12 | NUM_INPUTS 13 | }; 14 | enum OutputIds { 15 | NOIS_OUTPUT, 16 | NUM_OUTPUTS 17 | }; 18 | enum LightIds { 19 | NUM_LIGHTS 20 | }; 21 | 22 | PolyNosModule() : BaseModule() { 23 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 24 | } 25 | void process(const ProcessArgs &args) override; 26 | rack::dsp::SchmittTrigger clkTrigger; 27 | rack::dsp::SchmittTrigger resetTrigger; 28 | 29 | rack::dsp::PulseGenerator resetPulse; 30 | 31 | bool armed = false; 32 | 33 | // For more advanced Module features, read Rack's engine.hpp header file 34 | // - dataToJson, dataFromJson: serialization of internal data 35 | // - onSampleRateChange: event triggered by a change of sample rate 36 | // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu 37 | }; 38 | 39 | void PolyNosModule::process(const ProcessArgs &args) { 40 | 41 | auto channels = std::max(1, inputs[SRC_INPUT].getChannels()); 42 | 43 | outputs[NOIS_OUTPUT].setChannels(channels); 44 | for (int i = 0; i < channels; i++) { 45 | outputs[NOIS_OUTPUT].setVoltage((rack::random::uniform() - 0.5) * 10, i); 46 | } 47 | } 48 | 49 | struct PolyNosModuleWidget : BaseWidget { 50 | TextField *textField; 51 | 52 | PolyNosModuleWidget(PolyNosModule *module) { 53 | initColourChange(Rect(Vec(10, 10), Vec(100, 13)), module, 0.125f, 0.25f, 0.5f); 54 | setModule(module); 55 | 56 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/polynos.svg"))); 57 | 58 | addInput(createInputCentered(Vec(mm2px(5.08), mm2px(60.5)), module, PolyNosModule::SRC_INPUT)); 59 | 60 | addOutput(createOutputCentered(Vec(mm2px(5.08), mm2px(103.8)), module, PolyNosModule::NOIS_OUTPUT)); 61 | 62 | } 63 | }; 64 | 65 | 66 | // Specify the Module and ModuleWidget subclass, human-readable 67 | // author name for categorization per plugin, module slug (should never 68 | // change), human-readable module name, and any number of tags 69 | // (found in `include/tags.hpp`) separated by commas. 70 | Model *modelPolyNosModule = createModel("rcm-polynos"); 71 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "array": "cpp", 4 | "string_view": "cpp", 5 | "*.ipp": "cpp", 6 | "system_error": "cpp", 7 | "*.tcc": "cpp", 8 | "istream": "cpp", 9 | "optional": "cpp", 10 | "ostream": "cpp", 11 | "ratio": "cpp", 12 | "type_traits": "cpp", 13 | "cstdarg": "cpp", 14 | "atomic": "cpp", 15 | "hash_map": "cpp", 16 | "strstream": "cpp", 17 | "bitset": "cpp", 18 | "cctype": "cpp", 19 | "chrono": "cpp", 20 | "clocale": "cpp", 21 | "cmath": "cpp", 22 | "codecvt": "cpp", 23 | "complex": "cpp", 24 | "condition_variable": "cpp", 25 | "cstddef": "cpp", 26 | "cstdint": "cpp", 27 | "cstdio": "cpp", 28 | "cstdlib": "cpp", 29 | "cstring": "cpp", 30 | "ctime": "cpp", 31 | "cwchar": "cpp", 32 | "cwctype": "cpp", 33 | "deque": "cpp", 34 | "list": "cpp", 35 | "unordered_map": "cpp", 36 | "vector": "cpp", 37 | "exception": "cpp", 38 | "fstream": "cpp", 39 | "functional": "cpp", 40 | "initializer_list": "cpp", 41 | "iomanip": "cpp", 42 | "iosfwd": "cpp", 43 | "iostream": "cpp", 44 | "limits": "cpp", 45 | "memory": "cpp", 46 | "mutex": "cpp", 47 | "new": "cpp", 48 | "numeric": "cpp", 49 | "sstream": "cpp", 50 | "stdexcept": "cpp", 51 | "streambuf": "cpp", 52 | "thread": "cpp", 53 | "cinttypes": "cpp", 54 | "tuple": "cpp", 55 | "typeindex": "cpp", 56 | "typeinfo": "cpp", 57 | "utility": "cpp", 58 | "valarray": "cpp", 59 | "csignal": "cpp", 60 | "any": "cpp", 61 | "bit": "cpp", 62 | "compare": "cpp", 63 | "concepts": "cpp", 64 | "coroutine": "cpp", 65 | "forward_list": "cpp", 66 | "map": "cpp", 67 | "set": "cpp", 68 | "unordered_set": "cpp", 69 | "algorithm": "cpp", 70 | "iterator": "cpp", 71 | "memory_resource": "cpp", 72 | "random": "cpp", 73 | "regex": "cpp", 74 | "source_location": "cpp", 75 | "string": "cpp", 76 | "future": "cpp", 77 | "ranges": "cpp", 78 | "shared_mutex": "cpp", 79 | "stop_token": "cpp", 80 | "cfenv": "cpp", 81 | "variant": "cpp" 82 | }, 83 | "C_Cpp.default.includePath": [ 84 | "../../**" 85 | ], 86 | "C_Cpp.errorSquiggles": "Enabled", 87 | "C_Cpp.default.configurationProvider": "ms-vscode.makefile-tools" 88 | } -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "rcm", 3 | "name": "RCM", 4 | "brand": "RCM", 5 | "version": "2.0.0", 6 | "license": "GPL-2.0-or-later", 7 | "author": "RCM", 8 | "authorEmail": "rcmmodules@tupper.org.uk", 9 | "authorUrl": "https://github.com/Rcomian", 10 | "pluginUrl": "https://github.com/Rcomian/rcm-modules", 11 | "manualUrl": "https://github.com/Rcomian/rcm-modules/blob/v2/README.md", 12 | "sourceUrl": "https://github.com/Rcomian/rcm-modules", 13 | "donateUrl": "", 14 | "modules": [ 15 | { 16 | "slug": "rcm-CVTgl", 17 | "name": "CV Toggle", 18 | "description": "A labelled toggle switch, outputs 0V or 10V", 19 | "tags": ["Controller"] 20 | }, 21 | { 22 | "slug": "rcm-CVMmt", 23 | "name": "CV Momentary", 24 | "description": "A labelled button, outputs 0V or 10V", 25 | "tags": ["Controller"] 26 | }, 27 | { 28 | "slug": "rcm-CVS0to10", 29 | "name": "CV Sliders 0 to 10", 30 | "description": "4 CV sliders, outputs 0V to 10V", 31 | "tags": ["Controller"] 32 | }, 33 | { 34 | "slug": "rcm-CV5to5", 35 | "name": "CV Knob -5 to 5", 36 | "description": "CV knob, outputs -5V to 5V", 37 | "tags": ["Controller"] 38 | }, 39 | { 40 | "slug": "rcm-CV0to10", 41 | "name": "CV Knob 0 to 10", 42 | "description": "CV knob, outputs 0V to 10V", 43 | "tags": ["Controller"] 44 | }, 45 | { 46 | "slug": "rcm-gverb", 47 | "name": "GVerb", 48 | "description": "Dirty unstable reverb", 49 | "tags": ["Reverb"] 50 | }, 51 | { 52 | "slug": "rcm-duck", 53 | "name": "Duck", 54 | "description": "Basic audio ducking", 55 | "tags": ["Dynamics"] 56 | }, 57 | { 58 | "slug": "rcm-seq-adapter", 59 | "name": "SEQ Adapter", 60 | "description": "Nord style reset for syncing simple sequencers", 61 | "tags": ["Sequencer"] 62 | }, 63 | { 64 | "slug": "rcm-sync", 65 | "name": "Sync", 66 | "description": "Simplified Nord style reset for syncing simple sequencers", 67 | "tags": ["Sequencer"] 68 | }, 69 | { 70 | "slug": "rcm-polynos", 71 | "name": "Poly Noise", 72 | "description": "Polyphonic white noise output. Polyphony matches src input", 73 | "tags": ["Polyphonic", "Noise"] 74 | }, 75 | { 76 | "slug": "rcm-pianoroll", 77 | "name": "Piano Roll", 78 | "description": "Simple Piano Roll for sequencing", 79 | "tags": ["Sequencer"] 80 | } 81 | ] 82 | } -------------------------------------------------------------------------------- /src/SongRoll/PatternControllerSlice.cppx: -------------------------------------------------------------------------------- 1 | #include "../SongRoll/PatternControllerSlice.hpp" 2 | #include "../SongRoll/PatternHeaderWidget.hpp" 3 | #include "../SongRoll/PatternControlWidget.hpp" 4 | #include "../SongRoll/RepeatControlWidget.hpp" 5 | #include "../SongRoll/ClockDivControlWidget.hpp" 6 | #include "../SongRoll/SongRollData.hpp" 7 | #include "../Consts.hpp" 8 | 9 | namespace SongRoll { 10 | 11 | PatternControllerSlice::PatternControllerSlice(int channel, SongRollData& data, int section) : channel(channel), data(data), section(section) { 12 | layout = createWidget(Vec(0, 0)); 13 | layout->orientation = SequentialLayout::VERTICAL_ORIENTATION; 14 | layout->alignment = SequentialLayout::LEFT_ALIGNMENT; 15 | layout->spacing = 10; 16 | 17 | header = new PatternHeaderWidget(8, 2, 1); 18 | layout->addChild(header); 19 | 20 | pattern = createWidget(); 21 | layout->addChild(pattern); 22 | 23 | repeats = createWidget(); 24 | layout->addChild(repeats); 25 | 26 | clock_div = createWidget(); 27 | layout->addChild(clock_div); 28 | 29 | addChild(layout); 30 | } 31 | 32 | 33 | 34 | void PatternControllerSlice::step() { 35 | if (sectionChanged) { 36 | sectionChanged = false; 37 | pattern->pattern = data.sections[section].channels[channel].pattern; 38 | } else { 39 | data.sections[section].channels[channel].pattern = pattern->pattern; 40 | data.sections[section].channels[channel].repeats = repeats->repeats; 41 | data.sections[section].channels[channel].repeat_mode = (ChannelConfig::eRepeatMode)repeats->repeat_mode; 42 | data.sections[section].channels[channel].clock_div = clock_div->clock_div; 43 | } 44 | 45 | header->pattern = data.sections[section].channels[channel].pattern; 46 | header->repeats = data.sections[section].channels[channel].repeats; 47 | header->active = data.sections[section].channels[channel].clock_div > 0; 48 | 49 | for(auto *widget : layout->children) { 50 | widget->box.size.x = box.size.x; 51 | } 52 | 53 | layout->box.size.y = box.size.y; 54 | 55 | Widget::step(); 56 | } 57 | 58 | void PatternControllerSlice::draw(NVGcontext* ctx) { 59 | 60 | Widget::draw(ctx); 61 | return; 62 | 63 | } 64 | 65 | void PatternControllerSlice::onMouseDown(EventMouseDown& e) { 66 | Widget::onMouseDown(e); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/PianoRoll/DragModes.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | #include 3 | #include "../ModuleDragType.hpp" 4 | 5 | using namespace rack; 6 | 7 | class Auditioner; 8 | class PatternData; 9 | class Transport; 10 | struct WidgetState; 11 | class UnderlyingRollAreaWidget; 12 | 13 | struct PianoRollDragType : ModuleDragType { 14 | PianoRollDragType(); 15 | virtual ~PianoRollDragType(); 16 | }; 17 | 18 | 19 | struct PlayPositionDragging : public PianoRollDragType { 20 | Auditioner* auditioner; 21 | UnderlyingRollAreaWidget* widget; 22 | Transport* transport; 23 | 24 | PlayPositionDragging(Auditioner* auditioner, UnderlyingRollAreaWidget* widget, Transport* transport); 25 | virtual ~PlayPositionDragging(); 26 | 27 | void setNote(Vec mouseRel); 28 | void onDragMove(const rack::event::DragMove& e) override; 29 | }; 30 | 31 | struct LockMeasureDragging : public PianoRollDragType { 32 | std::chrono::time_point longPressStart; 33 | 34 | WidgetState* state; 35 | Transport* transport; 36 | 37 | LockMeasureDragging(WidgetState* state, Transport* transport); 38 | virtual ~LockMeasureDragging(); 39 | 40 | void onDragMove(const rack::event::DragMove& e) override; 41 | }; 42 | 43 | struct KeyboardDragging : public PianoRollDragType { 44 | float offset = 0; 45 | WidgetState* state; 46 | 47 | KeyboardDragging(WidgetState* state); 48 | virtual ~KeyboardDragging(); 49 | 50 | void onDragMove(const rack::event::DragMove& e) override; 51 | }; 52 | 53 | struct NotePaintDragging : public PianoRollDragType { 54 | int lastDragBeatDiv = -1000; 55 | int lastDragPitch = -1000; 56 | bool pitchLocked = false; 57 | bool makeStepsActive = true; 58 | int retriggerBeatDiv = 0; 59 | 60 | UnderlyingRollAreaWidget* widget; 61 | PatternData* patternData; 62 | Transport* transport; 63 | Auditioner* auditioner; 64 | 65 | NotePaintDragging(UnderlyingRollAreaWidget* widget, PatternData* patternData, Transport* transport, Auditioner* auditioner); 66 | virtual ~NotePaintDragging(); 67 | 68 | void onDragMove(const rack::event::DragMove& e) override; 69 | }; 70 | 71 | struct VelocityDragging : public PianoRollDragType { 72 | UnderlyingRollAreaWidget* widget; 73 | PatternData* patternData; 74 | Transport* transport; 75 | WidgetState* state; 76 | 77 | int pattern; 78 | int measure; 79 | int division; 80 | 81 | bool showLow = false; 82 | 83 | VelocityDragging(UnderlyingRollAreaWidget* widget, PatternData* patternData, Transport* transport, WidgetState* state, int pattern, int measure, int division); 84 | virtual ~VelocityDragging(); 85 | 86 | void onDragMove(const rack::event::DragMove& e) override; 87 | }; 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/SongRoll/RepeatControlWidget.cppx: -------------------------------------------------------------------------------- 1 | #include "../SongRoll/RepeatControlWidget.hpp" 2 | #include "../SongRoll/SongRollData.hpp" 3 | #include "../Consts.hpp" 4 | 5 | namespace SongRoll { 6 | 7 | RepeatControlWidget::RepeatControlWidget() {} 8 | 9 | void RepeatControlWidget::draw(NVGcontext* ctx) { 10 | float y = 1; 11 | 12 | nvgBeginPath(ctx); 13 | nvgFontSize(ctx, 10); 14 | nvgTextAlign(ctx, NVG_ALIGN_CENTER + NVG_ALIGN_TOP); 15 | nvgFillColor(ctx, NV_YELLOW); 16 | nvgText(ctx, box.size.x * 0.5, y, "-- Repeats --", NULL); 17 | 18 | y += 10; 19 | 20 | nvgBeginPath(ctx); 21 | nvgFontSize(ctx, 14); 22 | nvgTextAlign(ctx, NVG_ALIGN_CENTER + NVG_ALIGN_TOP); 23 | nvgFillColor(ctx, NV_YELLOW); 24 | nvgText(ctx, box.size.x * 0.25, y, "−", NULL); 25 | 26 | nvgBeginPath(ctx); 27 | nvgFontSize(ctx, 14); 28 | nvgTextAlign(ctx, NVG_ALIGN_CENTER + NVG_ALIGN_TOP); 29 | nvgFillColor(ctx, NV_YELLOW); 30 | nvgText(ctx, box.size.x * 0.75, y, "+", NULL); 31 | 32 | char repeatBuffer[100]; 33 | sprintf(repeatBuffer, "%d", repeats); 34 | 35 | nvgBeginPath(ctx); 36 | nvgFontSize(ctx, 14); 37 | nvgTextAlign(ctx, NVG_ALIGN_CENTER + NVG_ALIGN_TOP); 38 | nvgFillColor(ctx, NV_YELLOW); 39 | nvgText(ctx, box.size.x * 0.5, y, repeatBuffer, NULL); 40 | 41 | y += 16; 42 | 43 | nvgBeginPath(ctx); 44 | nvgFontSize(ctx, 12); 45 | nvgTextAlign(ctx, NVG_ALIGN_CENTER + NVG_ALIGN_TOP); 46 | nvgFillColor(ctx, NV_YELLOW); 47 | switch (repeat_mode) { 48 | case ChannelConfig::FREE: 49 | nvgText(ctx, box.size.x * 0.5, y, "no limit", NULL); 50 | break; 51 | case ChannelConfig::REPEATS: 52 | nvgText(ctx, box.size.x * 0.5, y, "at least", NULL); 53 | break; 54 | case ChannelConfig::LIMIT: 55 | nvgText(ctx, box.size.x * 0.5, y, "at most", NULL); 56 | break; 57 | } 58 | 59 | nvgBeginPath(ctx); 60 | nvgStrokeColor(ctx, NV_YELLOW_H); 61 | nvgRoundedRect(ctx, 8, y, box.size.x - 16, 12, 2); 62 | nvgStroke(ctx); 63 | 64 | y += 14; 65 | box.size.y = y; 66 | } 67 | 68 | void RepeatControlWidget::onMouseDown(EventMouseDown& e) { 69 | Rect minus(Vec(0,10), Vec(box.size.x*0.40,14)); 70 | Rect plus(Vec(box.size.x*0.60,10), Vec(box.size.x*0.40,14)); 71 | Rect mode(Vec(0, 26), Vec(box.size.x, 26)); 72 | 73 | if (minus.isContaining(e.pos)) { 74 | repeats = clamp(repeats - 1, 1, 64); 75 | } else if (plus.isContaining(e.pos)) { 76 | repeats = clamp(repeats + 1, 1, 64); 77 | } else if (mode.isContaining(e.pos)) { 78 | repeat_mode = (repeat_mode + 1) % 3; 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/CVMmt.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "BaseWidget.hpp" 3 | #include "ModuleTextWidget.hpp" 4 | 5 | struct CVMmtModule : BaseModule { 6 | enum ParamIds { 7 | BUTTON_PARAM, 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | NUM_INPUTS 12 | }; 13 | enum OutputIds { 14 | CV_OUTPUT, 15 | NUM_OUTPUTS 16 | }; 17 | enum LightIds { 18 | NUM_LIGHTS 19 | }; 20 | 21 | TextFieldModule textField; 22 | 23 | CVMmtModule() : BaseModule() { 24 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 25 | 26 | configParam(BUTTON_PARAM, 0.0, 10.0, 0.0); 27 | } 28 | void step() override; 29 | 30 | json_t* dataToJson() override { 31 | json_t* rootJ = BaseModule::dataToJson(); 32 | if (!rootJ) { 33 | rootJ = json_object(); 34 | } 35 | json_object_set_new(rootJ, "textfield", textField.dataToJson()); 36 | return rootJ; 37 | } 38 | 39 | void dataFromJson(json_t* rootJ) override { 40 | BaseModule::dataFromJson(rootJ); 41 | json_t* textfieldJ = json_object_get(rootJ, "textfield"); 42 | if (textfieldJ) { 43 | textField.dataFromJson(textfieldJ); 44 | } 45 | } 46 | }; 47 | 48 | 49 | void CVMmtModule::step() { 50 | outputs[CV_OUTPUT].setChannels(1); 51 | outputs[CV_OUTPUT].setVoltage(params[BUTTON_PARAM].value); 52 | } 53 | 54 | struct PB61303White : SvgSwitch { 55 | PB61303White() { 56 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/PB61303White.svg"))); 57 | } 58 | }; 59 | 60 | struct CVMmtModuleWidget : BaseWidget { 61 | TextFieldWidget *textField; 62 | 63 | CVMmtModuleWidget(CVMmtModule *module) { 64 | setModule(module); 65 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/CVMmt.svg"))); 66 | 67 | auto pbswitch = createParam(Vec(10, 156.23), module, CVMmtModule::BUTTON_PARAM); 68 | pbswitch->momentary = true; 69 | addParam(pbswitch); 70 | 71 | addOutput(createOutput(Vec(26, 331), module, CVMmtModule::CV_OUTPUT)); 72 | 73 | textField = createWidget(Vec(7.5, 38.0)); 74 | textField->box.size = Vec(60.0, 80.0); 75 | textField->multiline = true; 76 | ((LedDisplayTextField*)textField)->color = componentlibrary::SCHEME_WHITE; 77 | if (module) { 78 | textField->setModule(&module->textField); 79 | } 80 | addChild(textField); 81 | 82 | initColourChange(Rect(Vec(10, 10), Vec(50, 13)), module, 0.754f, 1.f, 0.58f); 83 | } 84 | 85 | }; 86 | 87 | 88 | // Specify the Module and ModuleWidget subclass, human-readable 89 | // author name for categorization per plugin, module slug (should never 90 | // change), human-readable module name, and any number of tags 91 | // (found in `include/tags.hpp`) separated by commas. 92 | Model *modelCVMmtModule = createModel("rcm-CVMmt"); 93 | -------------------------------------------------------------------------------- /src/CVTgl.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "BaseWidget.hpp" 3 | #include "ModuleTextWidget.hpp" 4 | 5 | struct CVTglModule : BaseModule { 6 | enum ParamIds { 7 | BUTTON_PARAM, 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | NUM_INPUTS 12 | }; 13 | enum OutputIds { 14 | CV_OUTPUT, 15 | NUM_OUTPUTS 16 | }; 17 | enum LightIds { 18 | NUM_LIGHTS 19 | }; 20 | 21 | TextFieldModule textField; 22 | 23 | CVTglModule() : BaseModule() { 24 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 25 | 26 | configParam(CVTglModule::BUTTON_PARAM, 0.0, 1.0, 0.0); 27 | } 28 | 29 | json_t* dataToJson() override { 30 | json_t* rootJ = BaseModule::dataToJson(); 31 | if (!rootJ) { 32 | rootJ = json_object(); 33 | } 34 | json_object_set_new(rootJ, "textfield", textField.dataToJson()); 35 | return rootJ; 36 | } 37 | 38 | void dataFromJson(json_t* rootJ) override { 39 | BaseModule::dataFromJson(rootJ); 40 | json_t* textfieldJ = json_object_get(rootJ, "textfield"); 41 | if (textfieldJ) { 42 | textField.dataFromJson(textfieldJ); 43 | } 44 | } 45 | 46 | void step() override; 47 | 48 | }; 49 | 50 | void CVTglModule::step() { 51 | outputs[CV_OUTPUT].setChannels(1); 52 | outputs[CV_OUTPUT].setVoltage(params[BUTTON_PARAM].value * 10.f); 53 | } 54 | 55 | struct CKSSWhite : SvgSwitch { 56 | CKSSWhite() { 57 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/CKSS_0_White.svg"))); 58 | addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/CKSS_1_White.svg"))); 59 | } 60 | }; 61 | 62 | struct CVTglModuleWidget : BaseWidget { 63 | TextFieldWidget *textField; 64 | 65 | CVTglModuleWidget(CVTglModule *module) { 66 | setModule(module); 67 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/CVTgl.svg"))); 68 | 69 | addParam(createParam(Vec(31, 172), module, CVTglModule::BUTTON_PARAM)); 70 | 71 | addOutput(createOutput(Vec(26, 331), module, CVTglModule::CV_OUTPUT)); 72 | 73 | textField = createWidget(Vec(7.5, 38.0)); 74 | textField->box.size = Vec(60.0, 80.0); 75 | textField->multiline = true; 76 | ((LedDisplayTextField*)textField)->color = componentlibrary::SCHEME_WHITE; 77 | if (module) { 78 | textField->setModule(&module->textField); 79 | } 80 | addChild(textField); 81 | 82 | initColourChange(Rect(Vec(10, 10), Vec(50, 13)), module, 0.754f, 1.f, 0.58f); 83 | } 84 | }; 85 | 86 | 87 | // Specify the Module and ModuleWidget subclass, human-readable 88 | // author name for categorization per plugin, module slug (should never 89 | // change), human-readable module name, and any number of tags 90 | // (found in `include/tags.hpp`) separated by commas. 91 | Model *modelCVTglModule = createModel("rcm-CVTgl"); 92 | -------------------------------------------------------------------------------- /src/CV5to5.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "BaseWidget.hpp" 3 | #include "ModuleTextWidget.hpp" 4 | 5 | struct CV5to5Module : BaseModule { 6 | enum ParamIds { 7 | AMOUNT_PARAM, 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | NUM_INPUTS 12 | }; 13 | enum OutputIds { 14 | CV_OUTPUT, 15 | NUM_OUTPUTS 16 | }; 17 | enum LightIds { 18 | NUM_LIGHTS 19 | }; 20 | 21 | TextFieldModule textField; 22 | 23 | CV5to5Module() : BaseModule() { 24 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 25 | configParam(CV5to5Module::AMOUNT_PARAM, -5.0, 5.0, 0.0); 26 | } 27 | void step() override; 28 | 29 | json_t* dataToJson() override { 30 | json_t* rootJ = BaseModule::dataToJson(); 31 | if (!rootJ) { 32 | rootJ = json_object(); 33 | } 34 | json_object_set_new(rootJ, "textfield", textField.dataToJson()); 35 | return rootJ; 36 | } 37 | 38 | void dataFromJson(json_t* rootJ) override { 39 | BaseModule::dataFromJson(rootJ); 40 | json_t* textfieldJ = json_object_get(rootJ, "textfield"); 41 | if (textfieldJ) { 42 | textField.dataFromJson(textfieldJ); 43 | } 44 | } 45 | 46 | // For more advanced Module features, read Rack's engine.hpp header file 47 | // - dataToJson, dataFromJson: serialization of internal data 48 | // - onSampleRateChange: event triggered by a change of sample rate 49 | // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu 50 | }; 51 | 52 | void CV5to5Module::step() { 53 | outputs[CV_OUTPUT].setChannels(1); 54 | outputs[CV_OUTPUT].setVoltage(params[AMOUNT_PARAM].value); 55 | } 56 | 57 | struct CV5to5ModuleWidget : BaseWidget { 58 | TextFieldWidget *textField; 59 | 60 | CV5to5ModuleWidget(CV5to5Module *module) { 61 | setModule(module); 62 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/CV5to5.svg"))); 63 | 64 | addParam(createParam(Vec(10, 156.23), module, CV5to5Module::AMOUNT_PARAM)); 65 | 66 | addOutput(createOutput(Vec(26, 331), module, CV5to5Module::CV_OUTPUT)); 67 | 68 | textField = createWidget(Vec(7.5, 38.0)); 69 | textField->box.size = Vec(60.0, 80.0); 70 | textField->multiline = true; 71 | ((LedDisplayTextField*)textField)->color = componentlibrary::SCHEME_WHITE; 72 | if (module) { 73 | textField->setModule(&module->textField); 74 | } 75 | addChild(textField); 76 | 77 | initColourChange(Rect(Vec(10, 10), Vec(50, 13)), module, 0.754f, 1.f, 0.58f); 78 | } 79 | 80 | }; 81 | 82 | 83 | // Specify the Module and ModuleWidget subclass, human-readable 84 | // author name for categorization per plugin, module slug (should never 85 | // change), human-readable module name, and any number of tags 86 | // (found in `include/tags.hpp`) separated by commas. 87 | Model *modelCV5to5Module = createModel("rcm-CV5to5"); 88 | -------------------------------------------------------------------------------- /src/CV0to10.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "BaseWidget.hpp" 3 | #include "ModuleTextWidget.hpp" 4 | 5 | struct CV0to10Module : BaseModule { 6 | enum ParamIds { 7 | AMOUNT_PARAM, 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | NUM_INPUTS 12 | }; 13 | enum OutputIds { 14 | CV_OUTPUT, 15 | NUM_OUTPUTS 16 | }; 17 | enum LightIds { 18 | NUM_LIGHTS 19 | }; 20 | 21 | TextFieldModule textField; 22 | 23 | CV0to10Module() : BaseModule() { 24 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 25 | configParam(CV0to10Module::AMOUNT_PARAM, 0.0, 10.0, 0.0); 26 | } 27 | void step() override; 28 | 29 | json_t* dataToJson() override { 30 | json_t* rootJ = BaseModule::dataToJson(); 31 | if (!rootJ) { 32 | rootJ = json_object(); 33 | } 34 | json_object_set_new(rootJ, "textfield", textField.dataToJson()); 35 | return rootJ; 36 | } 37 | 38 | void dataFromJson(json_t* rootJ) override { 39 | BaseModule::dataFromJson(rootJ); 40 | json_t* textfieldJ = json_object_get(rootJ, "textfield"); 41 | if (textfieldJ) { 42 | textField.dataFromJson(textfieldJ); 43 | } 44 | } 45 | 46 | // For more advanced Module features, read Rack's engine.hpp header file 47 | // - dataToJson, dataFromJson: serialization of internal data 48 | // - onSampleRateChange: event triggered by a change of sample rate 49 | // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu 50 | }; 51 | 52 | void CV0to10Module::step() { 53 | outputs[CV_OUTPUT].setChannels(1); 54 | outputs[CV_OUTPUT].setVoltage(params[AMOUNT_PARAM].value); 55 | } 56 | 57 | struct CV0to10ModuleWidget : BaseWidget { 58 | TextFieldWidget *textField; 59 | 60 | CV0to10ModuleWidget(CV0to10Module *module) { 61 | setModule(module); 62 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/CV0to10.svg"))); 63 | 64 | addParam(createParam(Vec(10, 156.23), module, CV0to10Module::AMOUNT_PARAM)); 65 | 66 | addOutput(createOutput(Vec(26, 331), module, CV0to10Module::CV_OUTPUT)); 67 | 68 | textField = createWidget(Vec(7.5, 38.0)); 69 | textField->box.size = Vec(60.0, 80.0); 70 | textField->multiline = true; 71 | ((LedDisplayTextField*)textField)->color = componentlibrary::SCHEME_WHITE; 72 | if (module) { 73 | textField->setModule(&module->textField); 74 | } 75 | addChild(textField); 76 | 77 | initColourChange(Rect(Vec(10, 10), Vec(50, 13)), module, 0.754f, 1.f, 0.58f); 78 | } 79 | 80 | }; 81 | 82 | 83 | // Specify the Module and ModuleWidget subclass, human-readable 84 | // author name for categorization per plugin, module slug (should never 85 | // change), human-readable module name, and any number of tags 86 | // (found in `include/tags.hpp`) separated by commas. 87 | Model *modelCV0to10Module = createModel("rcm-CV0to10"); 88 | -------------------------------------------------------------------------------- /src/PianoRoll/PatternData.hpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | #include 3 | 4 | struct Step; 5 | 6 | class PatternData { 7 | public: 8 | PatternData(); 9 | 10 | int getStepsInPattern(int pattern) const; 11 | int getStepsPerMeasure(int pattern) const; 12 | 13 | void setMeasures(int pattern, int measures); 14 | int getMeasures(int pattern) const; 15 | 16 | void setBeatsPerMeasure(int pattern, int beats); 17 | void setDivisionsPerBeat(int pattern, int divisions); 18 | 19 | int getBeatsPerMeasure(int pattern) const; 20 | int getDivisionsPerBeat(int pattern) const; 21 | 22 | void copyPattern(int pattern); 23 | void copyMeasure(int pattern, int measure); 24 | void pastePattern(int targetPattern); 25 | void pasteMeasure(int targetPattern, int targetMeasure); 26 | 27 | void toggleStepActive(int pattern, int measure, int step); 28 | void setStepActive(int pattern, int measure, int step, bool active); 29 | void setStepPitch(int pattern, int measure, int step, int pitch); 30 | void toggleStepRetrigger(int pattern, int measure, int step); 31 | void setStepRetrigger(int pattern, int measure, int step, bool retrigger); 32 | void setStepVelocity(int pattern, int measure, int step, float velocity); 33 | void increaseStepVelocityTo(int pattern, int measure, int step, float targetVelocity); 34 | 35 | bool isStepActive(int pattern, int measure, int step) const; 36 | bool isStepRetriggered(int pattern, int measure, int step) const; 37 | float getStepVelocity(int pattern, int measure, int step) const; 38 | int getStepPitch(int pattern, int measure, int step) const; 39 | 40 | float adjustVelocity(int pattern, int measure, int step, float delta); 41 | 42 | void clearPatternSteps(int pattern); 43 | void reset(); 44 | 45 | json_t *dataToJson() const; 46 | void dataFromJson(json_t *rootJ); 47 | 48 | bool dirty = true; 49 | bool consumeDirty(); 50 | 51 | struct Step { 52 | int pitch = 0; 53 | float velocity = 0.f; 54 | bool retrigger = false; 55 | bool active = false; 56 | }; 57 | 58 | struct Measure { 59 | std::vector steps; 60 | }; 61 | 62 | struct Pattern { 63 | std::vector measures; 64 | int numberOfMeasures = 1; 65 | int beatsPerMeasure = 4; 66 | int divisionsPerBeat = 4; 67 | }; 68 | 69 | std::vector patterns; 70 | 71 | void copyPatternData(const Pattern& sourcePattern, Pattern& targetPattern); 72 | void copyMeasureData(const Measure& sourceMeasure, Measure& targetMeasure); 73 | void copyStepData(const Step& sourceStep, Step& targetStep); 74 | 75 | void reassignSteps(int pattern, int fromSteps, int toSteps); 76 | 77 | Pattern copiedPattern; 78 | Measure copiedMeasure; 79 | int64_t moduleId = -1; 80 | 81 | struct PatternAction : rack::history::ModuleAction { 82 | PatternData::Pattern undoPatternData; 83 | PatternData::Pattern redoPatternData; 84 | int patternId; 85 | 86 | PatternAction(std::string name, int64_t moduleId, int patternId, PatternData& patternData); 87 | void undo() override; 88 | void redo() override; 89 | }; 90 | 91 | }; 92 | -------------------------------------------------------------------------------- /src/SongRoll/ClockDivControlWidget.cppx: -------------------------------------------------------------------------------- 1 | #include "../SongRoll/ClockDivControlWidget.hpp" 2 | #include "../Consts.hpp" 3 | 4 | namespace SongRoll { 5 | 6 | ClockDivControlWidget::ClockDivControlWidget() {} 7 | 8 | void ClockDivControlWidget::draw(NVGcontext* ctx) { 9 | float y = 1; 10 | 11 | nvgBeginPath(ctx); 12 | nvgFontSize(ctx, 10); 13 | nvgTextAlign(ctx, NVG_ALIGN_CENTER + NVG_ALIGN_TOP); 14 | nvgFillColor(ctx, NV_YELLOW); 15 | nvgText(ctx, box.size.x * 0.5, y, "-- Clock Div --", NULL); 16 | 17 | y += 10; 18 | 19 | float clockDivHeight = 48; 20 | float hcenter = box.size.x * 0.5; 21 | 22 | nvgBeginPath(ctx); 23 | nvgStrokeColor(ctx, NV_YELLOW_H); 24 | nvgStrokeWidth(ctx, 1.0f); 25 | nvgRoundedRect(ctx, 8, y, box.size.x - 16, clockDivHeight, 4); 26 | nvgStroke(ctx); 27 | 28 | nvgBeginPath(ctx); 29 | nvgStrokeColor(ctx, NV_YELLOW_H); 30 | nvgStrokeWidth(ctx, 2.0f); 31 | nvgFillColor(ctx, NV_BLACK); 32 | nvgRect(ctx, 9, y + (clockDivHeight / 2) - 10, box.size.x - 18, 20); 33 | nvgFill(ctx); 34 | 35 | nvgBeginPath(ctx); 36 | nvgStrokeColor(ctx, NV_YELLOW_H); 37 | nvgStrokeWidth(ctx, 1.0f); 38 | nvgMoveTo(ctx, 8, y + (clockDivHeight / 2) - 10); 39 | nvgLineTo(ctx, 8 + box.size.x - 16, y + (clockDivHeight / 2) - 10); 40 | nvgStroke(ctx); 41 | 42 | nvgBeginPath(ctx); 43 | nvgStrokeColor(ctx, NV_YELLOW_H); 44 | nvgStrokeWidth(ctx, 1.0f); 45 | nvgMoveTo(ctx, 8, y + (clockDivHeight / 2) - 10 + 20); 46 | nvgLineTo(ctx, 8 + box.size.x - 16, y + (clockDivHeight / 2) - 10 + 20); 47 | nvgStroke(ctx); 48 | 49 | nvgBeginPath(ctx); 50 | nvgStrokeColor(ctx, NV_YELLOW_H); 51 | nvgStrokeWidth(ctx, 3.0f); 52 | nvgMoveTo(ctx, hcenter - 8, y + 10); 53 | nvgLineTo(ctx, hcenter, y + 4); 54 | nvgLineTo(ctx, hcenter + 8, y + 10); 55 | nvgStroke(ctx); 56 | 57 | nvgBeginPath(ctx); 58 | nvgStrokeColor(ctx, NV_YELLOW_H); 59 | nvgStrokeWidth(ctx, 3.0f); 60 | nvgMoveTo(ctx, hcenter - 8, y + clockDivHeight - 10); 61 | nvgLineTo(ctx, hcenter, y + clockDivHeight - 4); 62 | nvgLineTo(ctx, hcenter + 8, y + clockDivHeight - 10); 63 | nvgStroke(ctx); 64 | 65 | 66 | char clockDivBuffer[100]; 67 | if (clock_div > 0) { 68 | sprintf(clockDivBuffer, "/ %d", clock_div); 69 | } else { 70 | sprintf(clockDivBuffer, "off"); 71 | } 72 | 73 | nvgBeginPath(ctx); 74 | nvgFontSize(ctx, 12); 75 | nvgTextAlign(ctx, NVG_ALIGN_CENTER + NVG_ALIGN_MIDDLE); 76 | nvgFillColor(ctx, NV_YELLOW); 77 | nvgText(ctx, hcenter, y + (clockDivHeight / 2), clockDivBuffer, NULL); 78 | 79 | y += clockDivHeight; 80 | 81 | box.size.y = y; 82 | } 83 | 84 | void ClockDivControlWidget::onMouseDown(EventMouseDown& e) { 85 | Rect minus(Vec(0,44), Vec(box.size.x,20)); 86 | Rect plus(Vec(0,10), Vec(box.size.x,20)); 87 | 88 | if (minus.isContaining(e.pos)) { 89 | clock_div = clamp(clock_div - 1, 0, 64); 90 | } else if (plus.isContaining(e.pos)) { 91 | clock_div = clamp(clock_div + 1, 0, 64); 92 | } 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /res/PB61303White.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 33 | 34 | 44 | 45 | 68 | 70 | 71 | 73 | image/svg+xml 74 | 76 | 77 | 78 | 79 | 80 | 85 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/gverbdsp.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* 4 | 5 | Copyright (C) 1999 Juhana Sadeharju 6 | kouhia at nic.funet.fi 7 | 8 | This program is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; either version 2 of the License, or 11 | (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 | 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "gverbdsp.h" 30 | 31 | #define TRUE 1 32 | #define FALSE 0 33 | 34 | ty_diffuser *diffuser_make(int size, float coeff) 35 | { 36 | ty_diffuser *p; 37 | int i; 38 | 39 | p = (ty_diffuser *)malloc(sizeof(ty_diffuser)); 40 | p->size = size; 41 | p->coeff = coeff; 42 | p->idx = 0; 43 | p->buf = (float *)malloc(size*sizeof(float)); 44 | for (i = 0; i < size; i++) p->buf[i] = 0.0; 45 | return(p); 46 | } 47 | 48 | void diffuser_free(ty_diffuser *p) 49 | { 50 | free(p->buf); 51 | free(p); 52 | } 53 | 54 | void diffuser_flush(ty_diffuser *p) 55 | { 56 | memset(p->buf, 0, p->size * sizeof(float)); 57 | } 58 | 59 | ty_damper *damper_make(float damping) 60 | { 61 | ty_damper *p; 62 | 63 | p = (ty_damper *)malloc(sizeof(ty_damper)); 64 | p->damping = damping; 65 | p->delay = 0.0f; 66 | return(p); 67 | } 68 | 69 | void damper_free(ty_damper *p) 70 | { 71 | free(p); 72 | } 73 | 74 | void damper_flush(ty_damper *p) 75 | { 76 | p->delay = 0.0f; 77 | } 78 | 79 | ty_fixeddelay *fixeddelay_make(int size) 80 | { 81 | ty_fixeddelay *p; 82 | int i; 83 | 84 | p = (ty_fixeddelay *)malloc(sizeof(ty_fixeddelay)); 85 | p->size = size; 86 | p->idx = 0; 87 | p->buf = (float *)malloc(size*sizeof(float)); 88 | for (i = 0; i < size; i++) p->buf[i] = 0.0; 89 | return(p); 90 | } 91 | 92 | void fixeddelay_free(ty_fixeddelay *p) 93 | { 94 | free(p->buf); 95 | free(p); 96 | } 97 | 98 | void fixeddelay_flush(ty_fixeddelay *p) 99 | { 100 | memset(p->buf, 0, p->size * sizeof(float)); 101 | } 102 | 103 | int isprime(int n) 104 | { 105 | unsigned int i; 106 | const unsigned int lim = (int)sqrtf((float)n); 107 | 108 | if (n == 2) return(TRUE); 109 | if ((n & 1) == 0) return(FALSE); 110 | for(i = 3; i <= lim; i += 2) 111 | if ((n % i) == 0) return(FALSE); 112 | return(TRUE); 113 | } 114 | 115 | int nearest_prime(int n, float rerror) 116 | /* relative error; new prime will be in range 117 | * [n-n*rerror, n+n*rerror]; 118 | */ 119 | { 120 | int bound,k; 121 | 122 | if (isprime(n)) return(n); 123 | /* assume n is large enough and n*rerror enough smaller than n */ 124 | bound = n*rerror; 125 | for(k = 1; k <= bound; k++) { 126 | if (isprime(n+k)) return(n+k); 127 | if (isprime(n-k)) return(n-k); 128 | } 129 | return(-1); 130 | } 131 | -------------------------------------------------------------------------------- /src/PianoRoll/RollAreaWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "rack.hpp" 3 | 4 | class Auditioner; 5 | class PatternData; 6 | class Transport; 7 | struct PianoRollDragType; 8 | 9 | struct Key { 10 | rack::Vec pos; 11 | rack::Vec size; 12 | bool sharp; 13 | int num; 14 | int oct; 15 | 16 | Key() : pos(rack::Vec(0,0)), size(rack::Vec(0,0)), sharp(false), num(0), oct(0) {} 17 | Key(rack::Vec p, rack::Vec s, bool sh, int n, int o) : pos(p), size(s), sharp(sh), num(n), oct(o) {} 18 | 19 | int pitch() { 20 | return num + (12 * oct); 21 | } 22 | }; 23 | 24 | struct BeatDiv { 25 | rack::Vec pos; 26 | rack::Vec size; 27 | int num; 28 | bool beat; 29 | bool triplet; 30 | 31 | BeatDiv() : pos(rack::Vec(0,0)), size(rack::Vec(0,0)), num(0), beat(false), triplet(false) {} 32 | }; 33 | 34 | struct WidgetState { 35 | rack::Rect box; 36 | int notesToShow = 18; 37 | int lowestDisplayNote = 4 * 12; 38 | int currentMeasure = 0; 39 | int lastDrawnStep = -1; 40 | float displayVelocityHigh = -1; 41 | float displayVelocityLow = -1; 42 | double measureLockPressTime = 0.f; 43 | 44 | bool dirty = true; 45 | bool consumeDirty(); 46 | }; 47 | 48 | class UnderlyingRollAreaWidget : public rack::Widget { 49 | public: 50 | UnderlyingRollAreaWidget(); 51 | ~UnderlyingRollAreaWidget(); 52 | 53 | std::string fontPath; 54 | 55 | WidgetState* state; 56 | PatternData* patternData; 57 | Transport* transport; 58 | Auditioner* auditioner; 59 | float topMargins = 15; 60 | 61 | std::vector getKeys(const rack::Rect& keysArea); 62 | std::vector getBeatDivs(const rack::math::Rect &roll); 63 | rack::Rect reserveKeysArea(rack::Rect& roll); 64 | std::tuple findMeasure(rack::Vec pos); 65 | std::tuple findOctaveSwitch(rack::Vec pos); 66 | std::tuple findCell(rack::Vec pos); 67 | 68 | void drawKeys(const rack::widget::Widget::DrawArgs &args, const std::vector &keys); 69 | void drawSwimLanes(const rack::widget::Widget::DrawArgs &args, const rack::Rect &roll, const std::vector &keys); 70 | void drawBeats(const rack::widget::Widget::DrawArgs &args, const std::vector &beatDivs); 71 | void drawNotes(const rack::widget::Widget::DrawArgs &args, const std::vector &keys, const std::vector &beatDivs); 72 | void drawMeasures(const rack::widget::Widget::DrawArgs &args); 73 | void drawPlayPosition(const rack::widget::Widget::DrawArgs &args); 74 | void drawVelocityInfo(const rack::widget::Widget::DrawArgs &args); 75 | void drawHalo(const DrawArgs &args, float x, float y, float w, float h); 76 | 77 | void draw(const rack::widget::Widget::DrawArgs &args) override; 78 | void drawLayer(const DrawArgs& args, int layer) override; 79 | 80 | void onButton(const rack::event::Button& e) override; 81 | void onDragStart(const rack::event::DragStart& e) override; 82 | void onDragMove(const rack::event::DragMove& e) override; 83 | void onDragEnd(const rack::event::DragEnd& e) override; 84 | 85 | rack::Vec lastMouseDown; 86 | PianoRollDragType* currentDragType = NULL; 87 | }; 88 | 89 | class RollAreaWidget : public rack::FramebufferWidget { 90 | public: 91 | 92 | WidgetState state; 93 | bool stateNeedsSaving = false; 94 | UnderlyingRollAreaWidget* underlyingRollAreaWidget; 95 | 96 | RollAreaWidget(PatternData* patternData, Transport* transport, Auditioner* auditioner); 97 | 98 | void step() override; 99 | 100 | private: 101 | PatternData* patternData; 102 | Transport* transport; 103 | }; 104 | 105 | -------------------------------------------------------------------------------- /src/DuckModule.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "BaseWidget.hpp" 3 | 4 | struct Follower { 5 | float level = 0.f; 6 | 7 | float process(const Module::ProcessArgs &args, float* left, float* right, float recovery) { 8 | auto value = std::max(abs(*left), abs(*right)); 9 | 10 | if (value >= level) { 11 | level = value; 12 | } else { 13 | level -= (level - value) / (args.sampleRate * recovery); 14 | } 15 | 16 | return clampSafe(level / 10.f, 0.f, 1.f); 17 | } 18 | }; 19 | 20 | struct DuckModule : BaseModule { 21 | enum ParamIds { 22 | AMOUNT_PARAM, 23 | RECOVERY_PARAM, 24 | NUM_PARAMS 25 | }; 26 | enum InputIds { 27 | LEFT_OVER_AUDIO, 28 | RIGHT_OVER_AUDIO, 29 | LEFT_UNDER_AUDIO, 30 | RIGHT_UNDER_AUDIO, 31 | NUM_INPUTS 32 | }; 33 | enum OutputIds { 34 | LEFT_OUTPUT, 35 | RIGHT_OUTPUT, 36 | NUM_OUTPUTS 37 | }; 38 | enum LightIds { 39 | NUM_LIGHTS 40 | }; 41 | 42 | DuckModule() : BaseModule() { 43 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 44 | 45 | configParam(AMOUNT_PARAM, 0.0, 2.0, 1.0); 46 | configParam(RECOVERY_PARAM, 0.0, 1.0, 0.5); 47 | } 48 | void process(const ProcessArgs &args) override; 49 | 50 | Follower follower; 51 | 52 | // For more advanced Module features, read Rack's engine.hpp header file 53 | // - dataToJson, dataFromJson: serialization of internal data 54 | // - onSampleRateChange: event triggered by a change of sample rate 55 | // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu 56 | }; 57 | 58 | void DuckModule::process(const ProcessArgs &args) { 59 | auto leftOverAudioIn = inputs[LEFT_OVER_AUDIO].getVoltageSum(); 60 | auto rightOverAudioIn = inputs[RIGHT_OVER_AUDIO].getVoltageSum(); 61 | auto leftUnderAudioIn = inputs[LEFT_UNDER_AUDIO].getVoltageSum(); 62 | auto rightUnderAudioIn = inputs[RIGHT_UNDER_AUDIO].getVoltageSum(); 63 | 64 | auto amount = clampSafe(follower.process(args, &leftOverAudioIn, &rightOverAudioIn, params[RECOVERY_PARAM].value) * pow(params[AMOUNT_PARAM].value, 2.0), 0.f, 1.f); 65 | 66 | outputs[LEFT_OUTPUT].setVoltage(crossfade(leftUnderAudioIn, leftOverAudioIn, amount)); 67 | outputs[RIGHT_OUTPUT].setVoltage(crossfade(rightUnderAudioIn, rightOverAudioIn, amount)); 68 | } 69 | 70 | struct DuckModuleWidget : BaseWidget { 71 | DuckModuleWidget(DuckModule *module) { 72 | setModule(module); 73 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Duck.svg"))); 74 | 75 | addParam(createParam(Vec(10, 65), module, DuckModule::AMOUNT_PARAM)); 76 | addParam(createParam(Vec(10, 166), module, DuckModule::RECOVERY_PARAM)); 77 | 78 | addInput(createInput(Vec(12, 257), module, DuckModule::LEFT_OVER_AUDIO)); 79 | addInput(createInput(Vec(40, 257), module, DuckModule::RIGHT_OVER_AUDIO)); 80 | 81 | addInput(createInput(Vec(12, 295), module, DuckModule::LEFT_UNDER_AUDIO)); 82 | addInput(createInput(Vec(40, 295), module, DuckModule::RIGHT_UNDER_AUDIO)); 83 | 84 | addOutput(createOutput(Vec(12, 338), module, DuckModule::LEFT_OUTPUT)); 85 | addOutput(createOutput(Vec(40, 338), module, DuckModule::RIGHT_OUTPUT)); 86 | 87 | initColourChange(Rect(Vec(21.785, 10), Vec(37.278, 13)), module, 0.58f, 1.f, 0.58f); 88 | } 89 | }; 90 | 91 | 92 | // Specify the Module and ModuleWidget subclass, human-readable 93 | // author name for categorization per plugin, module slug (should never 94 | // change), human-readable module name, and any number of tags 95 | // (found in `include/tags.hpp`) separated by commas. 96 | Model *modelDuckModule = createModel("rcm-duck"); 97 | -------------------------------------------------------------------------------- /src/CVS0to10.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "BaseWidget.hpp" 3 | #include "ModuleTextWidget.hpp" 4 | 5 | struct CVS0to10Module : BaseModule { 6 | enum ParamIds { 7 | AMOUNT_PARAM_A, 8 | AMOUNT_PARAM_B, 9 | AMOUNT_PARAM_C, 10 | AMOUNT_PARAM_D, 11 | NUM_PARAMS 12 | }; 13 | enum InputIds { 14 | NUM_INPUTS 15 | }; 16 | enum OutputIds { 17 | CV_OUTPUT_A, 18 | CV_OUTPUT_B, 19 | CV_OUTPUT_C, 20 | CV_OUTPUT_D, 21 | NUM_OUTPUTS 22 | }; 23 | enum LightIds { 24 | NUM_LIGHTS 25 | }; 26 | 27 | TextFieldModule textField; 28 | 29 | CVS0to10Module() : BaseModule() { 30 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 31 | configParam(AMOUNT_PARAM_A, 0.0, 10.0, 0.0); 32 | configParam(AMOUNT_PARAM_B, 0.0, 10.0, 0.0); 33 | configParam(AMOUNT_PARAM_C, 0.0, 10.0, 0.0); 34 | configParam(AMOUNT_PARAM_D, 0.0, 10.0, 0.0); 35 | } 36 | void step() override; 37 | 38 | json_t* dataToJson() override { 39 | json_t* rootJ = BaseModule::dataToJson(); 40 | if (!rootJ) { 41 | rootJ = json_object(); 42 | } 43 | json_object_set_new(rootJ, "textfield", textField.dataToJson()); 44 | return rootJ; 45 | } 46 | 47 | void dataFromJson(json_t* rootJ) override { 48 | BaseModule::dataFromJson(rootJ); 49 | json_t* textfieldJ = json_object_get(rootJ, "textfield"); 50 | if (textfieldJ) { 51 | textField.dataFromJson(textfieldJ); 52 | } 53 | } 54 | 55 | // For more advanced Module features, read Rack's engine.hpp header file 56 | // - dataToJson, dataFromJson: serialization of internal data 57 | // - onSampleRateChange: event triggered by a change of sample rate 58 | // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu 59 | }; 60 | 61 | void CVS0to10Module::step() { 62 | outputs[CV_OUTPUT_A].setChannels(1); 63 | outputs[CV_OUTPUT_B].setChannels(1); 64 | outputs[CV_OUTPUT_C].setChannels(1); 65 | outputs[CV_OUTPUT_D].setChannels(1); 66 | 67 | outputs[CV_OUTPUT_A].setVoltage(params[AMOUNT_PARAM_A].value); 68 | outputs[CV_OUTPUT_B].setVoltage(params[AMOUNT_PARAM_B].value); 69 | outputs[CV_OUTPUT_C].setVoltage(params[AMOUNT_PARAM_C].value); 70 | outputs[CV_OUTPUT_D].setVoltage(params[AMOUNT_PARAM_D].value); 71 | } 72 | 73 | struct CVS0to10ModuleWidget : BaseWidget { 74 | TextFieldWidget *textField; 75 | 76 | CVS0to10ModuleWidget(CVS0to10Module *module) { 77 | setModule(module); 78 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/CVS0to10.svg"))); 79 | 80 | auto x = 6.f; 81 | addParam(createParam(Vec(11.5-x, 135), module, CVS0to10Module::AMOUNT_PARAM_A)); 82 | addParam(createParam(Vec(26.0-x, 135), module, CVS0to10Module::AMOUNT_PARAM_B)); 83 | addParam(createParam(Vec(40.5-x, 135), module, CVS0to10Module::AMOUNT_PARAM_C)); 84 | addParam(createParam(Vec(55.0-x, 135), module, CVS0to10Module::AMOUNT_PARAM_D)); 85 | 86 | addOutput(createOutput(Vec(12.5, 278), module, CVS0to10Module::CV_OUTPUT_A)); 87 | addOutput(createOutput(Vec(42, 278), module, CVS0to10Module::CV_OUTPUT_B)); 88 | addOutput(createOutput(Vec(12.5, 317), module, CVS0to10Module::CV_OUTPUT_C)); 89 | addOutput(createOutput(Vec(42, 317), module, CVS0to10Module::CV_OUTPUT_D)); 90 | 91 | textField = createWidget(Vec(7.5, 38.0)); 92 | textField->box.size = Vec(60.0, 80.0); 93 | textField->multiline = true; 94 | ((LedDisplayTextField*)textField)->color = componentlibrary::SCHEME_WHITE; 95 | if (module) { 96 | textField->setModule(&module->textField); 97 | } 98 | addChild(textField); 99 | 100 | 101 | initColourChange(Rect(Vec(10, 10), Vec(50, 13)), module, 0.754f, 1.f, 0.58f); 102 | } 103 | 104 | }; 105 | 106 | 107 | // Specify the Module and ModuleWidget subclass, human-readable 108 | // author name for categorization per plugin, module slug (should never 109 | // change), human-readable module name, and any number of tags 110 | // (found in `include/tags.hpp`) separated by commas. 111 | Model *modelCVS0to10Module = createModel("rcm-CVS0to10"); 112 | -------------------------------------------------------------------------------- /src/PianoRoll/Transport.cpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | #include "../PianoRoll/Transport.hpp" 3 | #include "../PianoRoll/PatternData.hpp" 4 | 5 | using namespace rack; 6 | 7 | Transport::Transport(PatternData* patternData) { 8 | this->patternData = patternData; 9 | 10 | locked = false; 11 | running = true; 12 | recording = false; 13 | pendingRecording = false; 14 | } 15 | 16 | int Transport::currentPattern() { 17 | return pattern; 18 | } 19 | 20 | int Transport::currentMeasure() { 21 | return stepInPattern / patternData->getStepsPerMeasure(pattern); 22 | } 23 | 24 | int Transport::currentStepInMeasure() { 25 | return stepInPattern % patternData->getStepsPerMeasure(pattern); 26 | } 27 | 28 | int Transport::currentStepInPattern() { 29 | return stepInPattern; 30 | } 31 | 32 | bool Transport::isLastStepOfPattern() { 33 | return stepInPattern == (patternData->getStepsInPattern(pattern) - 1); 34 | } 35 | 36 | void Transport::setPattern(int pattern) { 37 | pattern = clamp(pattern, 0, 63); 38 | if (pattern != this->pattern) { 39 | dirty = true; 40 | this->pattern = pattern; 41 | if (this->stepInPattern != 0) { 42 | this->stepInPattern = -1; 43 | } 44 | } 45 | } 46 | 47 | void Transport::setMeasure(int measure) { 48 | int firstStepInPattern = patternData->getStepsPerMeasure(pattern) * measure; 49 | int lastStepInPattern = firstStepInPattern + patternData->getStepsPerMeasure(pattern) - 1; 50 | if (stepInPattern < firstStepInPattern || stepInPattern > lastStepInPattern) { 51 | dirty = true; 52 | stepInPattern = lastStepInPattern; 53 | } 54 | } 55 | 56 | void Transport::setStepInMeasure(int stepInMeasure) { 57 | int newStepInPattern = (currentMeasure() * patternData->getStepsPerMeasure(pattern)) + (stepInMeasure % patternData->getStepsPerMeasure(pattern)); 58 | dirty |= (newStepInPattern != stepInPattern); 59 | stepInPattern = newStepInPattern; 60 | } 61 | 62 | void Transport::setStepInPattern(int stepInPattern) { 63 | dirty |= (this->stepInPattern != stepInPattern); 64 | this->stepInPattern = stepInPattern; 65 | } 66 | 67 | void Transport::advancePattern(int offset) { 68 | dirty |= (offset != 0); 69 | setPattern(pattern + offset); 70 | } 71 | 72 | void Transport::advanceStep() { 73 | if (!running) { 74 | return; 75 | } 76 | 77 | dirty = true; 78 | 79 | int firstStepInLoop = 0; 80 | int measure = currentMeasure(); 81 | stepInPattern = (stepInPattern + 1) % patternData->getStepsInPattern(pattern); 82 | int newMeasure = currentMeasure(); 83 | 84 | if (locked) { 85 | firstStepInLoop = measure * patternData->getStepsPerMeasure(pattern); 86 | 87 | if (measure != newMeasure) { 88 | // We moved on to another measure in the pattern, adjust back to the start of our locked measure 89 | stepInPattern = firstStepInLoop; 90 | } 91 | } 92 | 93 | if (recording && stepInPattern == firstStepInLoop) { 94 | pendingRecording = false; 95 | recording = false; 96 | } 97 | 98 | if (pendingRecording && stepInPattern == firstStepInLoop) { 99 | pendingRecording = false; 100 | recording = true; 101 | } 102 | } 103 | 104 | void Transport::lockMeasure() { 105 | dirty |= (locked == false); 106 | locked = true; 107 | } 108 | 109 | void Transport::unlockMeasure() { 110 | dirty |= (locked == true); 111 | locked = false; 112 | } 113 | 114 | bool Transport::isLocked() { 115 | return locked; 116 | } 117 | 118 | void Transport::toggleRun() { 119 | dirty = true; 120 | running = !running; 121 | } 122 | 123 | void Transport::setRun(bool running) { 124 | dirty |= this->running != running; 125 | this->running = running; 126 | } 127 | 128 | bool Transport::isRunning() { 129 | return running; 130 | } 131 | 132 | void Transport::toggleRecording() { 133 | dirty = true; 134 | if (!recording) { 135 | pendingRecording = !pendingRecording; 136 | } else { 137 | recording = false; 138 | pendingRecording = false; 139 | } 140 | } 141 | 142 | bool Transport::isRecording() { 143 | return recording; 144 | } 145 | 146 | bool Transport::isPendingRecording() { 147 | return pendingRecording; 148 | } 149 | 150 | void Transport::reset() { 151 | dirty = true; 152 | stepInPattern = -1; 153 | } 154 | 155 | bool Transport::consumeDirty() { 156 | bool wasdirty = dirty; 157 | dirty = false; 158 | return wasdirty; 159 | } 160 | -------------------------------------------------------------------------------- /src/Sync.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "BaseWidget.hpp" 3 | 4 | 5 | 6 | struct SyncModule : BaseModule { 7 | enum ParamIds { 8 | NUM_PARAMS 9 | }; 10 | enum InputIds { 11 | CLK_INPUT, 12 | RESET_INPUT, 13 | NUM_INPUTS 14 | }; 15 | enum OutputIds { 16 | CLK_OUTPUT, 17 | RESET_OUTPUT, 18 | NUM_OUTPUTS 19 | }; 20 | enum LightIds { 21 | CLK_LIGHT, 22 | ARMED_LIGHT, 23 | NUM_LIGHTS 24 | }; 25 | 26 | SyncModule() : BaseModule() { 27 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 28 | } 29 | void process(const ProcessArgs &args) override; 30 | rack::dsp::SchmittTrigger clkTrigger; 31 | rack::dsp::SchmittTrigger resetTrigger; 32 | bool suppressClock = false; 33 | 34 | rack::dsp::PulseGenerator resetPulse; 35 | 36 | bool armed = false; 37 | bool noClkOnReset = false; 38 | 39 | json_t *dataToJson() override { 40 | json_t *rootJ = BaseModule::dataToJson(); 41 | if (rootJ == NULL) { 42 | rootJ = json_object(); 43 | } 44 | 45 | json_object_set_new(rootJ, "noClkOnReset", json_boolean(noClkOnReset)); 46 | 47 | return rootJ; 48 | } 49 | 50 | void dataFromJson(json_t *rootJ) override { 51 | BaseModule::dataFromJson(rootJ); 52 | 53 | json_t *noClkOnResetJ = json_object_get(rootJ, "noClkOnReset"); 54 | if (noClkOnResetJ) { 55 | noClkOnReset = json_boolean_value(noClkOnResetJ); 56 | } 57 | } 58 | 59 | // For more advanced Module features, read Rack's engine.hpp header file 60 | // - dataToJson, dataFromJson: serialization of internal data 61 | // - onSampleRateChange: event triggered by a change of sample rate 62 | // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu 63 | }; 64 | 65 | void SyncModule::process(const ProcessArgs &args) { 66 | 67 | auto clk = inputs[CLK_INPUT].getVoltage(); 68 | auto reset = inputs[RESET_INPUT].getVoltage(); 69 | 70 | bool clkTriggered = clkTrigger.process(clk); 71 | if (clkTriggered) { 72 | suppressClock = false; 73 | } 74 | bool resetTriggered = resetTrigger.process(reset); 75 | 76 | if (clkTriggered && armed) { 77 | armed = false; 78 | resetPulse.trigger(); 79 | } 80 | 81 | if (resetTriggered) { 82 | if (clk == 0.f) { 83 | armed = true; 84 | } else { 85 | resetPulse.trigger(); 86 | } 87 | } 88 | 89 | bool resetPulseHigh = resetPulse.process(args.sampleTime); 90 | if (resetPulseHigh) { 91 | suppressClock = true; 92 | } 93 | 94 | outputs[RESET_OUTPUT].setVoltage(resetPulseHigh ? 10.f : 0.f); 95 | outputs[CLK_OUTPUT].setVoltage( (noClkOnReset && suppressClock) ? 0.f : inputs[CLK_INPUT].getVoltage()); 96 | 97 | lights[CLK_LIGHT].value = inputs[CLK_INPUT].getVoltage() ? 1.0 : 0.0; 98 | lights[ARMED_LIGHT].value = armed ? 1.0 : 0.0; 99 | } 100 | 101 | struct SyncModuleWidget : BaseWidget { 102 | TextField *textField; 103 | 104 | SyncModuleWidget(SyncModule *module) { 105 | setModule(module); 106 | initColourChange(Rect(Vec(10, 10), Vec(100, 13)), module, 1.f, 0.6f, 0.4f); 107 | 108 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/sync.svg"))); 109 | 110 | addInput(createInputCentered(Vec(mm2px(5.08), mm2px(60.5)), module, SyncModule::CLK_INPUT)); 111 | addInput(createInputCentered(Vec(mm2px(5.08), mm2px(74.02)), module, SyncModule::RESET_INPUT)); 112 | 113 | addOutput(createOutputCentered(Vec(mm2px(5.08), mm2px(103.8)), module, SyncModule::CLK_OUTPUT)); 114 | addOutput(createOutputCentered(Vec(mm2px(5.08), mm2px(117.5)), module, SyncModule::RESET_OUTPUT)); 115 | 116 | addChild(createLightCentered>(Vec(mm2px(5.08), mm2px(18.5)), module, SyncModule::CLK_LIGHT)); 117 | addChild(createLightCentered>(Vec(mm2px(5.08), mm2px(36)), module, SyncModule::ARMED_LIGHT)); 118 | } 119 | 120 | void appendContextMenu(Menu* menu) override { 121 | menu->addChild(new MenuSeparator); 122 | if (module) { 123 | menu->addChild(createBoolPtrMenuItem("No CLK on Reset", "", &((SyncModule*)module)->noClkOnReset)); 124 | } 125 | 126 | } 127 | 128 | }; 129 | 130 | 131 | // Specify the Module and ModuleWidget subclass, human-readable 132 | // author name for categorization per plugin, module slug (should never 133 | // change), human-readable module name, and any number of tags 134 | // (found in `include/tags.hpp`) separated by commas. 135 | Model *modelSyncModule = createModel("rcm-sync"); 136 | -------------------------------------------------------------------------------- /src/BaseWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "BaseWidget.hpp" 2 | #include "dsp/window.hpp" 3 | 4 | BaseWidget::BaseWidget() : ModuleWidget() { 5 | box.size.y = RACK_GRID_HEIGHT; 6 | } 7 | 8 | static const float COLOURDRAG_SENSITIVITY = 0.0015f; 9 | 10 | void ColourChangeWidget::onButton(const event::Button &e) { 11 | Widget::onButton(e); 12 | e.stopPropagating(); 13 | if (e.button == GLFW_MOUSE_BUTTON_LEFT && (APP->window->getMods() & GLFW_MOD_SHIFT)) { 14 | // Consume if not consumed by child 15 | if (!e.isConsumed()) 16 | e.consume(this); 17 | } 18 | } 19 | 20 | void ColourChangeWidget::onDragStart(const event::DragStart& e) { 21 | if ((APP->window->getMods() & GLFW_MOD_SHIFT) == GLFW_MOD_SHIFT) { 22 | dragging = true; 23 | APP->window->cursorLock(); 24 | } 25 | 26 | Widget::onDragStart(e); 27 | } 28 | 29 | void ColourChangeWidget::onDragMove(const event::DragMove& e) { 30 | if (dragging) { 31 | float speed = 1.f; 32 | float range = 1.f; 33 | 34 | float delta = COLOURDRAG_SENSITIVITY * e.mouseDelta.y * speed * range; 35 | if (APP->window->getMods() & GLFW_MOD_CONTROL) { 36 | delta /= 16.f; 37 | } 38 | 39 | if (colourData) { 40 | colourData->backgroundHue = clamp(colourData->backgroundHue + delta, 0.f, 1.f); 41 | colourData->dirty = true; 42 | } 43 | } else { 44 | Widget::onDragMove(e); 45 | } 46 | } 47 | 48 | void ColourChangeWidget::onDragEnd(const event::DragEnd& e) { 49 | if (dragging) { 50 | dragging = false; 51 | APP->window->cursorUnlock(); 52 | } 53 | 54 | Widget::onDragEnd(e); 55 | } 56 | 57 | json_t *BaseModule::dataToJson() { 58 | json_t *rootJ = Module::dataToJson(); 59 | if (rootJ == NULL) { 60 | rootJ = json_object(); 61 | } 62 | 63 | json_object_set_new(rootJ, "backgroundHue", json_real(colourData.backgroundHue)); 64 | json_object_set_new(rootJ, "backgroundSaturation", json_real(colourData.backgroundSaturation)); 65 | json_object_set_new(rootJ, "backgroundLuminosity", json_real(colourData.backgroundLuminosity)); 66 | 67 | return rootJ; 68 | } 69 | 70 | void BaseModule::dataFromJson(json_t *rootJ) { 71 | Module::dataFromJson(rootJ); 72 | 73 | json_t *backgroundHueJ = json_object_get(rootJ, "backgroundHue"); 74 | if (backgroundHueJ) { 75 | colourData.backgroundHue = json_real_value(backgroundHueJ); 76 | colourData.dirty = true; 77 | } 78 | 79 | json_t *backgroundSaturationJ = json_object_get(rootJ, "backgroundSaturation"); 80 | if (backgroundSaturationJ) { 81 | colourData.backgroundSaturation = json_real_value(backgroundSaturationJ); 82 | colourData.dirty = true; 83 | } 84 | 85 | json_t *backgroundLuminosityJ = json_object_get(rootJ, "backgroundLuminosity"); 86 | if (backgroundLuminosityJ) { 87 | colourData.backgroundLuminosity = json_real_value(backgroundLuminosityJ); 88 | colourData.dirty = true; 89 | } 90 | } 91 | 92 | void BaseWidget::initColourChange(Rect hotspot, BaseModule* baseModule, float hue, float saturation, float luminosity) { 93 | colourData.backgroundHue = hue; 94 | colourData.backgroundSaturation = saturation; 95 | colourData.backgroundLuminosity = luminosity; 96 | 97 | if (baseModule) { 98 | moduleColourData = &baseModule->colourData; 99 | 100 | if (!moduleColourData->dirty) { 101 | moduleColourData->backgroundHue = hue; 102 | moduleColourData->backgroundSaturation = saturation; 103 | moduleColourData->backgroundLuminosity = luminosity; 104 | } 105 | } 106 | 107 | auto colourChangeWidget = createWidget(hotspot.pos); 108 | colourChangeWidget->box.size = hotspot.size; 109 | colourChangeWidget->colourData = moduleColourData; 110 | addChild(colourChangeWidget); 111 | } 112 | 113 | void BaseWidget::step() { 114 | if (moduleColourData && moduleColourData->dirty) { 115 | colourData.backgroundHue = moduleColourData->backgroundHue; 116 | colourData.backgroundLuminosity = moduleColourData->backgroundLuminosity; 117 | colourData.backgroundSaturation = moduleColourData->backgroundSaturation; 118 | moduleColourData->dirty = false; 119 | } 120 | 121 | ModuleWidget::step(); 122 | } 123 | 124 | void BaseWidget::draw(const DrawArgs& args) { 125 | nvgBeginPath(args.vg); 126 | nvgFillColor(args.vg, nvgHSL(colourData.backgroundHue, colourData.backgroundSaturation, colourData.backgroundLuminosity)); 127 | nvgRect(args.vg, 0, 0, box.size.x, box.size.y); 128 | nvgFill(args.vg); 129 | 130 | ModuleWidget::draw(args); 131 | } 132 | 133 | -------------------------------------------------------------------------------- /res/CKSS_0_White.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 66 | 70 | 73 | 78 | 79 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /res/CKSS_1_White.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 66 | 70 | 73 | 78 | 79 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/SEQAdapter.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "BaseWidget.hpp" 3 | 4 | struct SchmittResetTrigger { 5 | enum State { 6 | UNKNOWN, 7 | LOW, 8 | HIGH 9 | }; 10 | State state; 11 | 12 | SchmittResetTrigger() { 13 | reset(); 14 | } 15 | void reset() { 16 | state = UNKNOWN; 17 | } 18 | /** Updates the state of the Schmitt Trigger given a value. 19 | Returns true if triggered, i.e. the value increases from 0 to 1. 20 | If different trigger thresholds are needed, use 21 | process(rescale(in, low, high, 0.f, 1.f)) 22 | for example. 23 | */ 24 | bool process(float in) { 25 | switch (state) { 26 | case LOW: 27 | if (in >= 1.f) { 28 | state = HIGH; 29 | } 30 | break; 31 | case HIGH: 32 | if (in <= 0.f) { 33 | state = LOW; 34 | return true; 35 | } 36 | break; 37 | default: 38 | if (in >= 1.f) { 39 | state = HIGH; 40 | } 41 | else if (in <= 0.f) { 42 | state = LOW; 43 | } 44 | break; 45 | } 46 | return false; 47 | } 48 | bool isHigh() { 49 | return state == HIGH; 50 | } 51 | }; 52 | 53 | 54 | struct SEQAdapterModule : BaseModule { 55 | enum ParamIds { 56 | NUM_PARAMS 57 | }; 58 | enum InputIds { 59 | RUN_INPUT, 60 | CLK_INPUT, 61 | RESET_INPUT, 62 | NUM_INPUTS 63 | }; 64 | enum OutputIds { 65 | RUN_OUTPUT, 66 | CLK_OUTPUT, 67 | RESET_OUTPUT, 68 | NUM_OUTPUTS 69 | }; 70 | enum LightIds { 71 | RUN_LIGHT, 72 | ARMED_LIGHT, 73 | NUM_LIGHTS 74 | }; 75 | 76 | SEQAdapterModule() : BaseModule() { 77 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 78 | } 79 | void step() override; 80 | rack::dsp::SchmittTrigger runTrigger; 81 | SchmittResetTrigger clkResetTrigger; 82 | rack::dsp::SchmittTrigger resetTrigger; 83 | 84 | bool running = false; 85 | bool redirectNextClk = false; 86 | bool receivedClockWhenStopped = false; 87 | 88 | // For more advanced Module features, read Rack's engine.hpp header file 89 | // - dataToJson, dataFromJson: serialization of internal data 90 | // - onSampleRateChange: event triggered by a change of sample rate 91 | // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu 92 | }; 93 | 94 | void SEQAdapterModule::step() { 95 | 96 | bool runTriggered = runTrigger.process(inputs[RUN_INPUT].getVoltage()); 97 | bool resetTriggered = resetTrigger.process(inputs[RESET_INPUT].getVoltage()); 98 | bool clkReset = clkResetTrigger.process(inputs[CLK_INPUT].getVoltage()); 99 | 100 | if (runTriggered) { 101 | running = !running; 102 | receivedClockWhenStopped = false; 103 | } 104 | else if (clkReset && !resetTriggered) { 105 | if (receivedClockWhenStopped) { 106 | running = true; 107 | } else { 108 | receivedClockWhenStopped = true; 109 | } 110 | redirectNextClk = false; 111 | } 112 | 113 | if (resetTriggered && !running) { 114 | redirectNextClk = true; 115 | } 116 | 117 | float redirectedResetValue = redirectNextClk ? inputs[CLK_INPUT].getVoltage() : inputs[RESET_INPUT].getVoltage(); 118 | float passThroughResetValue = running ? redirectedResetValue : 0; 119 | 120 | outputs[RUN_OUTPUT].setVoltage(inputs[RUN_INPUT].getVoltage()); 121 | outputs[RESET_OUTPUT].setVoltage(passThroughResetValue); 122 | outputs[CLK_OUTPUT].setVoltage(inputs[CLK_INPUT].getVoltage()); 123 | 124 | lights[RUN_LIGHT].value = running ? 1.0 : 0.0; 125 | lights[ARMED_LIGHT].value = redirectNextClk ? 1.0 : 0.0; 126 | } 127 | 128 | struct SEQAdapterModuleWidget : BaseWidget { 129 | TextField *textField; 130 | 131 | SEQAdapterModuleWidget(SEQAdapterModule *module) { 132 | setModule(module); 133 | initColourChange(Rect(Vec(10, 10), Vec(100, 13)), module, 0.528f, 0.6f, 0.4f); 134 | 135 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/seqadapter.svg"))); 136 | 137 | addInput(createInput(Vec(12, 380-22.977-92), module, SEQAdapterModule::RESET_INPUT)); 138 | addInput(createInput(Vec(48, 380-22.977-92), module, SEQAdapterModule::RUN_INPUT)); 139 | addInput(createInput(Vec(83, 380-22.977-92), module, SEQAdapterModule::CLK_INPUT)); 140 | 141 | addOutput(createOutput(Vec(30.5, 380-22.977-20), module, SEQAdapterModule::RESET_OUTPUT)); 142 | addOutput(createOutput(Vec(65.5, 380-22.977-20), module, SEQAdapterModule::CLK_OUTPUT)); 143 | 144 | addChild(createLight>(Vec(86, 70), module, SEQAdapterModule::RUN_LIGHT)); 145 | addChild(createLight>(Vec(86, 85), module, SEQAdapterModule::ARMED_LIGHT)); 146 | } 147 | }; 148 | 149 | 150 | // Specify the Module and ModuleWidget subclass, human-readable 151 | // author name for categorization per plugin, module slug (should never 152 | // change), human-readable module name, and any number of tags 153 | // (found in `include/tags.hpp`) separated by commas. 154 | Model *modelSEQAdapterModule = createModel("rcm-seq-adapter"); 155 | -------------------------------------------------------------------------------- /src/ladspa-util.h: -------------------------------------------------------------------------------- 1 | /* Some misc util functions for audio DSP work, written by Steve Harris, 2 | * December 2000 3 | * 4 | * steve@plugin.org.uk 5 | */ 6 | 7 | #ifndef LADSPA_UTIL_H 8 | #define LADSPA_UTIL_H 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define buffer_write(a, b) a=(b) 15 | 16 | // 16.16 fixpoint 17 | typedef union { 18 | int32_t all; 19 | struct { 20 | #ifdef WORDS_BIGENDIAN 21 | int16_t in; 22 | uint16_t fr; 23 | #else 24 | uint16_t fr; 25 | int16_t in; 26 | #endif 27 | } part; 28 | } fixp16; 29 | 30 | // 32.32 fixpoint 31 | typedef union { 32 | int64_t all; 33 | struct { 34 | #ifdef WORDS_BIGENDIAN 35 | int32_t in; 36 | uint32_t fr; 37 | #else 38 | uint32_t fr; 39 | int32_t in; 40 | #endif 41 | } part; 42 | } fixp32; 43 | 44 | /* 32 bit "pointer cast" union */ 45 | typedef union { 46 | float f; 47 | int32_t i; 48 | } ls_pcast32; 49 | 50 | // Sometimes it doesn't get defined, even though it eists and C99 is declared 51 | long int lrintf (float x); 52 | 53 | // 1.0 / ln(2) 54 | #define LN2R 1.442695041f 55 | 56 | /* detet floating point denormal numbers by comparing them to the smallest 57 | * normal, crap, but reliable */ 58 | #define DN_CHECK(x, l) if (fabs(x) < 1e-38) printf("DN: " l"\n") 59 | 60 | // Denormalise floats, only actually needed for PIII and recent PowerPC 61 | //#define FLUSH_TO_ZERO(fv) (((*(unsigned int*)&(fv))&0x7f800000)==0)?0.0f:(fv) 62 | 63 | static __inline float flush_to_zero(float f) 64 | { 65 | ls_pcast32 v; 66 | 67 | v.f = f; 68 | 69 | // original: return (v.i & 0x7f800000) == 0 ? 0.0f : f; 70 | // version from Tim Blechmann 71 | return (v.i & 0x7f800000) < 0x08000000 ? 0.0f : f; 72 | } 73 | 74 | static __inline void round_to_zero(volatile float *f) 75 | { 76 | *f += 1e-18; 77 | *f -= 1e-18; 78 | } 79 | 80 | /* A set of branchless clipping operations from Laurent de Soras */ 81 | 82 | static __inline float f_max(float x, float a) 83 | { 84 | x -= a; 85 | x += fabs(x); 86 | x *= 0.5; 87 | x += a; 88 | 89 | return x; 90 | } 91 | 92 | static __inline float f_min(float x, float b) 93 | { 94 | x = b - x; 95 | x += fabs(x); 96 | x *= 0.5; 97 | x = b - x; 98 | 99 | return x; 100 | } 101 | 102 | static __inline float f_clamp(float x, float a, float b) 103 | { 104 | const float x1 = fabs(x - a); 105 | const float x2 = fabs(x - b); 106 | 107 | x = x1 + a + b; 108 | x -= x2; 109 | x *= 0.5; 110 | 111 | return x; 112 | } 113 | 114 | // Limit a value to be l<=v<=u 115 | #define LIMIT(v,l,u) ((v)<(l)?(l):((v)>(u)?(u):(v))) 116 | 117 | // Truncate-to-zero modulo (ANSI C doesn't specify) will only work 118 | // if -m < v < 2m 119 | #define MOD(v,m) (v<0?v+m:(v>=m?v-m:v)) 120 | 121 | // Truncate-to-zero modulo (ANSI C doesn't specify) will only work 122 | // if v > -m and v < m 123 | #define NEG_MOD(v,m) ((v)<0?((v)+(m)):(v)) 124 | 125 | // Convert a value in dB's to a coefficent 126 | #define DB_CO(g) ((g) > -90.0f ? powf(10.0f, (g) * 0.05f) : 0.0f) 127 | #define CO_DB(v) (20.0f * log10f(v)) 128 | 129 | // Linearly interpolate [ = a * (1 - f) + b * f] 130 | #define LIN_INTERP(f,a,b) ((a) + (f) * ((b) - (a))) 131 | 132 | // Cubic interpolation function 133 | static __inline float cube_interp(const float fr, const float inm1, const float 134 | in, const float inp1, const float inp2) 135 | { 136 | return in + 0.5f * fr * (inp1 - inm1 + 137 | fr * (4.0f * inp1 + 2.0f * inm1 - 5.0f * in - inp2 + 138 | fr * (3.0f * (in - inp1) - inm1 + inp2))); 139 | } 140 | 141 | /* fast sin^2 aproxiamtion, adapted from jan AT rpgfan's posting to the 142 | * music-dsp list */ 143 | static __inline float f_sin_sq(float angle) 144 | { 145 | const float asqr = angle * angle; 146 | float result = -2.39e-08f; 147 | 148 | result *= asqr; 149 | result += 2.7526e-06f; 150 | result *= asqr; 151 | result -= 1.98409e-04f; 152 | result *= asqr; 153 | result += 8.3333315e-03f; 154 | result *= asqr; 155 | result -= 1.666666664e-01f; 156 | result *= asqr; 157 | result += 1.0f; 158 | result *= angle; 159 | 160 | return result * result; 161 | } 162 | 163 | #ifdef HAVE_LRINTF 164 | 165 | #define f_round(f) lrintf(f) 166 | 167 | #else 168 | 169 | // Round float to int using IEEE int* hack 170 | static __inline int f_round(float f) 171 | { 172 | ls_pcast32 p; 173 | 174 | p.f = f; 175 | p.f += (3<<22); 176 | 177 | return p.i - 0x4b400000; 178 | } 179 | 180 | #endif 181 | 182 | // Truncate float to int 183 | static __inline int f_trunc(float f) 184 | { 185 | return f_round(floorf(f)); 186 | } 187 | 188 | /* Andrew Simper's pow(2, x) aproximation from the music-dsp list */ 189 | 190 | #if 0 191 | 192 | /* original */ 193 | static __inline float f_pow2(float x) 194 | { 195 | long *px = (long*)(&x); // store address of float as long pointer 196 | const float tx = (x-0.5f) + (3<<22); // temporary value for truncation 197 | const long lx = *((long*)&tx) - 0x4b400000; // integer power of 2 198 | const float dx = x-(float)(lx); // float remainder of power of 2 199 | 200 | x = 1.0f + dx*(0.6960656421638072f + // cubic apporoximation of 2^x 201 | dx*(0.224494337302845f + // for x in the range [0, 1] 202 | dx*(0.07944023841053369f))); 203 | *px += (lx<<23); // add integer power of 2 to exponent 204 | 205 | return x; 206 | } 207 | 208 | #else 209 | 210 | /* union version */ 211 | static __inline float f_pow2(float x) 212 | { 213 | ls_pcast32 *px, tx, lx; 214 | float dx; 215 | 216 | px = (ls_pcast32 *)&x; // store address of float as long pointer 217 | tx.f = (x-0.5f) + (3<<22); // temporary value for truncation 218 | lx.i = tx.i - 0x4b400000; // integer power of 2 219 | dx = x - (float)lx.i; // float remainder of power of 2 220 | 221 | x = 1.0f + dx * (0.6960656421638072f + // cubic apporoximation of 2^x 222 | dx * (0.224494337302845f + // for x in the range [0, 1] 223 | dx * (0.07944023841053369f))); 224 | (*px).i += (lx.i << 23); // add integer power of 2 to exponent 225 | 226 | return (*px).f; 227 | } 228 | 229 | #endif 230 | 231 | /* Fast exponentiation function, y = e^x */ 232 | #define f_exp(x) f_pow2(x * LN2R) 233 | 234 | #endif 235 | -------------------------------------------------------------------------------- /src/gverb.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 1999 Juhana Sadeharju 4 | kouhia at nic.funet.fi 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; if not, write to the Free Software 18 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 | 20 | */ 21 | 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "gverbdsp.h" 28 | #include "gverb.h" 29 | #include "ladspa-util.h" 30 | 31 | ty_gverb *gverb_new(int srate, float maxroomsize, float roomsize, 32 | float revtime, 33 | float damping, float spread, 34 | float inputbandwidth, float earlylevel, 35 | float taillevel) 36 | { 37 | ty_gverb *p; 38 | float ga,gb,gt; 39 | int i,n; 40 | float r; 41 | float diffscale; 42 | int a,b,c,cc,d,dd,e; 43 | float spread1,spread2; 44 | 45 | p = (ty_gverb *)malloc(sizeof(ty_gverb)); 46 | p->rate = srate; 47 | p->fdndamping = damping; 48 | p->maxroomsize = maxroomsize; 49 | p->roomsize = roomsize; 50 | p->revtime = revtime; 51 | p->earlylevel = earlylevel; 52 | p->taillevel = taillevel; 53 | 54 | p->maxdelay = p->rate*p->maxroomsize/340.0; 55 | p->largestdelay = p->rate*p->roomsize/340.0; 56 | 57 | 58 | /* Input damper */ 59 | 60 | p->inputbandwidth = inputbandwidth; 61 | p->inputdamper = damper_make(1.0 - p->inputbandwidth); 62 | 63 | 64 | /* FDN section */ 65 | 66 | 67 | p->fdndels = (ty_fixeddelay **)calloc(FDNORDER, sizeof(ty_fixeddelay *)); 68 | for(i = 0; i < FDNORDER; i++) { 69 | p->fdndels[i] = fixeddelay_make((int)p->maxdelay+1000); 70 | } 71 | p->fdngains = (float *)calloc(FDNORDER, sizeof(float)); 72 | p->fdnlens = (int *)calloc(FDNORDER, sizeof(int)); 73 | 74 | p->fdndamps = (ty_damper **)calloc(FDNORDER, sizeof(ty_damper *)); 75 | for(i = 0; i < FDNORDER; i++) { 76 | p->fdndamps[i] = damper_make(p->fdndamping); 77 | } 78 | 79 | ga = 60.0; 80 | gt = p->revtime; 81 | ga = powf(10.0f,-ga/20.0f); 82 | n = p->rate*gt; 83 | p->alpha = pow((double)ga, 1.0/(double)n); 84 | 85 | gb = 0.0; 86 | for(i = 0; i < FDNORDER; i++) { 87 | if (i == 0) gb = 1.000000*p->largestdelay; 88 | if (i == 1) gb = 0.816490*p->largestdelay; 89 | if (i == 2) gb = 0.707100*p->largestdelay; 90 | if (i == 3) gb = 0.632450*p->largestdelay; 91 | 92 | #if 0 93 | p->fdnlens[i] = nearest_prime((int)gb, 0.5); 94 | #else 95 | p->fdnlens[i] = f_round(gb); 96 | #endif 97 | p->fdngains[i] = -powf((float)p->alpha,p->fdnlens[i]); 98 | } 99 | 100 | p->d = (float *)calloc(FDNORDER, sizeof(float)); 101 | p->u = (float *)calloc(FDNORDER, sizeof(float)); 102 | p->f = (float *)calloc(FDNORDER, sizeof(float)); 103 | 104 | /* Diffuser section */ 105 | 106 | diffscale = (float)p->fdnlens[3]/(210+159+562+410); 107 | spread1 = spread; 108 | spread2 = 3.0*spread; 109 | 110 | b = 210; 111 | r = 0.125541; 112 | a = spread1*r; 113 | c = 210+159+a; 114 | cc = c-b; 115 | r = 0.854046; 116 | a = spread2*r; 117 | d = 210+159+562+a; 118 | dd = d-c; 119 | e = 1341-d; 120 | 121 | p->ldifs = (ty_diffuser **)calloc(4, sizeof(ty_diffuser *)); 122 | p->ldifs[0] = diffuser_make((int)(diffscale*b),0.75); 123 | p->ldifs[1] = diffuser_make((int)(diffscale*cc),0.75); 124 | p->ldifs[2] = diffuser_make((int)(diffscale*dd),0.625); 125 | p->ldifs[3] = diffuser_make((int)(diffscale*e),0.625); 126 | 127 | b = 210; 128 | r = -0.568366; 129 | a = spread1*r; 130 | c = 210+159+a; 131 | cc = c-b; 132 | r = -0.126815; 133 | a = spread2*r; 134 | d = 210+159+562+a; 135 | dd = d-c; 136 | e = 1341-d; 137 | 138 | p->rdifs = (ty_diffuser **)calloc(4, sizeof(ty_diffuser *)); 139 | p->rdifs[0] = diffuser_make((int)(diffscale*b),0.75); 140 | p->rdifs[1] = diffuser_make((int)(diffscale*cc),0.75); 141 | p->rdifs[2] = diffuser_make((int)(diffscale*dd),0.625); 142 | p->rdifs[3] = diffuser_make((int)(diffscale*e),0.625); 143 | 144 | 145 | 146 | /* Tapped delay section */ 147 | 148 | p->tapdelay = fixeddelay_make(44000); 149 | p->taps = (int *)calloc(FDNORDER, sizeof(int)); 150 | p->tapgains = (float *)calloc(FDNORDER, sizeof(float)); 151 | 152 | p->taps[0] = 5+0.410*p->largestdelay; 153 | p->taps[1] = 5+0.300*p->largestdelay; 154 | p->taps[2] = 5+0.155*p->largestdelay; 155 | p->taps[3] = 5+0.000*p->largestdelay; 156 | 157 | for(i = 0; i < FDNORDER; i++) { 158 | p->tapgains[i] = pow(p->alpha,(double)p->taps[i]); 159 | } 160 | 161 | return(p); 162 | } 163 | 164 | void gverb_free(ty_gverb *p) 165 | { 166 | int i; 167 | 168 | damper_free(p->inputdamper); 169 | for(i = 0; i < FDNORDER; i++) { 170 | fixeddelay_free(p->fdndels[i]); 171 | damper_free(p->fdndamps[i]); 172 | diffuser_free(p->ldifs[i]); 173 | diffuser_free(p->rdifs[i]); 174 | } 175 | free(p->fdndels); 176 | free(p->fdngains); 177 | free(p->fdnlens); 178 | free(p->fdndamps); 179 | free(p->d); 180 | free(p->u); 181 | free(p->f); 182 | free(p->ldifs); 183 | free(p->rdifs); 184 | free(p->taps); 185 | free(p->tapgains); 186 | fixeddelay_free(p->tapdelay); 187 | free(p); 188 | } 189 | 190 | void gverb_flush(ty_gverb *p) 191 | { 192 | int i; 193 | 194 | damper_flush(p->inputdamper); 195 | for(i = 0; i < FDNORDER; i++) { 196 | fixeddelay_flush(p->fdndels[i]); 197 | damper_flush(p->fdndamps[i]); 198 | diffuser_flush(p->ldifs[i]); 199 | diffuser_flush(p->rdifs[i]); 200 | } 201 | memset(p->d, 0, FDNORDER * sizeof(float)); 202 | memset(p->u, 0, FDNORDER * sizeof(float)); 203 | memset(p->f, 0, FDNORDER * sizeof(float)); 204 | fixeddelay_flush(p->tapdelay); 205 | } 206 | 207 | /* swh: other functions are now in the .h file for inlining */ 208 | -------------------------------------------------------------------------------- /src/SongRoll/SongRollWidget.cppx: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | #include "window.hpp" 3 | #include "../SongRoll/SongRollWidget.hpp" 4 | #include "../SongRoll/SongRollModule.hpp" 5 | #include "../SongRoll/DragModes.hpp" 6 | #include "../SongRoll/RollArea.hpp" 7 | #include "../Consts.hpp" 8 | 9 | using namespace rack; 10 | using namespace SongRoll; 11 | 12 | extern Plugin* plugin; 13 | 14 | static const int NUM_CHANNELS = 8; 15 | 16 | 17 | SongRollWidget::SongRollWidget(SongRollModule *module) : BaseWidget(module) { 18 | this->module = (SongRollModule*)module; 19 | 20 | colourHotZone = Rect(Vec(506, 10), Vec(85, 13)); 21 | backgroundHue = 0.33f; 22 | backgroundSaturation = 1.f; 23 | backgroundLuminosity = 0.25f; 24 | 25 | setPanel(SVG::load(assetPlugin(plugin, "res/SongRoll.svg"))); 26 | 27 | // addInput(createPort(Vec(50.114, 380.f-91-23.6), PortWidget::INPUT, module, SongRollModule::CLOCK_INPUT)); 28 | // addInput(createPort(Vec(85.642, 380.f-91-23.6), PortWidget::INPUT, module, SongRollModule::RESET_INPUT)); 29 | // addInput(createPort(Vec(121.170, 380.f-91-23.6), PortWidget::INPUT, module, SongRollModule::PATTERN_INPUT)); 30 | // addInput(createPort(Vec(156.697, 380.f-91-23.6), PortWidget::INPUT, module, SongRollModule::RUN_INPUT)); 31 | // addInput(createPort(Vec(192.224, 380.f-91-23.6), PortWidget::INPUT, module, SongRollModule::RECORD_INPUT)); 32 | 33 | // addInput(createPort(Vec(421.394, 380.f-91-23.6), PortWidget::INPUT, module, SongRollModule::VOCT_INPUT)); 34 | // addInput(createPort(Vec(456.921, 380.f-91-23.6), PortWidget::INPUT, module, SongRollModule::GATE_INPUT)); 35 | // addInput(createPort(Vec(492.448, 380.f-91-23.6), PortWidget::INPUT, module, SongRollModule::RETRIGGER_INPUT)); 36 | // addInput(createPort(Vec(527.976, 380.f-91-23.6), PortWidget::INPUT, module, SongRollModule::VELOCITY_INPUT)); 37 | 38 | // addOutput(createPort(Vec(50.114, 380.f-25.9-23.6), PortWidget::OUTPUT, module, SongRollModule::CLOCK_OUTPUT)); 39 | // addOutput(createPort(Vec(85.642, 380.f-25.9-23.6), PortWidget::OUTPUT, module, SongRollModule::RESET_OUTPUT)); 40 | // addOutput(createPort(Vec(121.170, 380.f-25.9-23.6), PortWidget::OUTPUT, module, SongRollModule::PATTERN_OUTPUT)); 41 | // addOutput(createPort(Vec(156.697, 380.f-25.9-23.6), PortWidget::OUTPUT, module, SongRollModule::RUN_OUTPUT)); 42 | // addOutput(createPort(Vec(192.224, 380.f-25.9-23.6), PortWidget::OUTPUT, module, SongRollModule::RECORD_OUTPUT)); 43 | 44 | // addOutput(createPort(Vec(421.394, 380.f-25.9-23.6), PortWidget::OUTPUT, module, SongRollModule::VOCT_OUTPUT)); 45 | // addOutput(createPortwidget = this; 48 | // addChild(patternWidget); 49 | 50 | auto *rollArea = new RollArea(getRollArea(), module->songRollData); 51 | addChild(rollArea); 52 | } 53 | 54 | void SongRollWidget::appendContextMenu(Menu* menu) { 55 | 56 | } 57 | 58 | Rect SongRollWidget::getRollArea() { 59 | return Rect(Vec(16, 380-218-145), Vec(477, 217)); 60 | } 61 | 62 | void SongRollWidget::drawBackgroundColour(NVGcontext* ctx) { 63 | nvgBeginPath(ctx); 64 | nvgFillColor(ctx, nvgHSL(backgroundHue, backgroundSaturation, backgroundLuminosity)); 65 | nvgRect(ctx, 0, 0, box.size.x, box.size.y); 66 | nvgFill(ctx); 67 | } 68 | 69 | static int stepcount = 0; 70 | void SongRollWidget::drawPatternEditors(NVGcontext* ctx) { 71 | //stepcount += 1; 72 | 73 | Rect rollArea = getRollArea(); 74 | static const float PATTERN_AREA_HEIGHT = 1; 75 | static const float leftMargin = 0; 76 | // Rect patternArea(Vec(rollArea.pos.x, rollArea.pos.y + rollArea.size.y - (rollArea.size.y * PATTERN_AREA_HEIGHT)), Vec(rollArea.size.x, rollArea.size.y * PATTERN_AREA_HEIGHT)); 77 | Rect patternArea(Vec(rollArea.pos.x + leftMargin, rollArea.pos.y + rollArea.size.y - (rollArea.size.y * PATTERN_AREA_HEIGHT)), Vec(rollArea.size.x - leftMargin, rollArea.size.y * PATTERN_AREA_HEIGHT)); 78 | 79 | nvgBeginPath(ctx); 80 | nvgFillColor(ctx, nvgRGBA(1, 1, 1, 1)); 81 | nvgRect(ctx, rollArea.pos.x, rollArea.pos.y, rollArea.size.x, rollArea.size.y); 82 | nvgFill(ctx); 83 | 84 | 85 | float channelWidth = patternArea.size.x / NUM_CHANNELS; 86 | 87 | nvgSave(ctx); 88 | nvgScissor(ctx, patternArea.pos.x, patternArea.pos.y, patternArea.size.x, patternArea.size.y); 89 | 90 | for(int i = 1; i < NUM_CHANNELS; i++) { 91 | nvgBeginPath(ctx); 92 | nvgMoveTo(ctx, patternArea.pos.x + (channelWidth * i), patternArea.pos.y); 93 | nvgLineTo(ctx, patternArea.pos.x + (channelWidth * i), patternArea.pos.y + patternArea.size.y); 94 | nvgStrokeWidth(ctx, 1.f); 95 | nvgStrokeColor(ctx, NV_YELLOW_H); 96 | nvgStroke(ctx); 97 | } 98 | 99 | nvgRestore(ctx); 100 | } 101 | 102 | void SongRollWidget::draw(NVGcontext* ctx) { 103 | drawBackgroundColour(ctx); 104 | 105 | BaseWidget::draw(ctx); 106 | 107 | drawPatternEditors(ctx); 108 | } 109 | 110 | struct ClickZone { 111 | Rect r; 112 | 113 | }; 114 | 115 | //void std::vector< 116 | 117 | void SongRollWidget::onMouseDown(EventMouseDown& e) { 118 | Vec pos = gRackWidget->lastMousePos.minus(box.pos); 119 | 120 | Rect repeatsMinus(Vec(), Vec()); 121 | Rect repeatsPlus(Vec(), Vec()); 122 | Rect modeFree(Vec(), Vec()); 123 | Rect modeRepeats(Vec(), Vec()); 124 | Rect modeLimit(Vec(), Vec()); 125 | 126 | 127 | BaseWidget::onMouseDown(e); 128 | } 129 | 130 | void SongRollWidget::onDragStart(EventDragStart& e) { 131 | Vec pos = gRackWidget->lastMousePos.minus(box.pos); 132 | 133 | BaseWidget::onDragStart(e); 134 | } 135 | 136 | void SongRollWidget::baseDragMove(EventDragMove& e) { 137 | BaseWidget::onDragMove(e); 138 | } 139 | 140 | void SongRollWidget::onDragMove(EventDragMove& e) { 141 | BaseWidget::onDragMove(e); 142 | } 143 | 144 | void SongRollWidget::onDragEnd(EventDragEnd& e) { 145 | BaseWidget::onDragEnd(e); 146 | } 147 | 148 | json_t *SongRollWidget::dataToJson() { 149 | json_t *rootJ = BaseWidget::dataToJson(); 150 | if (rootJ == NULL) { 151 | rootJ = json_object(); 152 | } 153 | 154 | return rootJ; 155 | } 156 | 157 | void SongRollWidget::dataFromJson(json_t *rootJ) { 158 | BaseWidget::dataFromJson(rootJ); 159 | 160 | } 161 | 162 | 163 | -------------------------------------------------------------------------------- /src/gverb.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 1999 Juhana Sadeharju 4 | kouhia at nic.funet.fi 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; if not, write to the Free Software 18 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 | 20 | */ 21 | 22 | #ifndef GVERB_H 23 | #define GVERB_H 24 | 25 | #include 26 | #include 27 | #include 28 | #include "gverbdsp.h" 29 | #include "gverb.h" 30 | #include "ladspa-util.h" 31 | 32 | #define FDNORDER 4 33 | 34 | typedef struct { 35 | int rate; 36 | float inputbandwidth; 37 | float taillevel; 38 | float earlylevel; 39 | ty_damper *inputdamper; 40 | float maxroomsize; 41 | float roomsize; 42 | float revtime; 43 | float maxdelay; 44 | float largestdelay; 45 | ty_fixeddelay **fdndels; 46 | float *fdngains; 47 | int *fdnlens; 48 | ty_damper **fdndamps; 49 | float fdndamping; 50 | ty_diffuser **ldifs; 51 | ty_diffuser **rdifs; 52 | ty_fixeddelay *tapdelay; 53 | int *taps; 54 | float *tapgains; 55 | float *d; 56 | float *u; 57 | float *f; 58 | double alpha; 59 | } ty_gverb; 60 | 61 | 62 | ty_gverb *gverb_new(int, float, float, float, float, float, float, float, float); 63 | void gverb_free(ty_gverb *); 64 | void gverb_flush(ty_gverb *); 65 | static void gverb_do(ty_gverb *, float, float *, float *); 66 | static void gverb_set_roomsize(ty_gverb *, float); 67 | static void gverb_set_revtime(ty_gverb *, float); 68 | static void gverb_set_damping(ty_gverb *, float); 69 | static void gverb_set_inputbandwidth(ty_gverb *, float); 70 | static void gverb_set_earlylevel(ty_gverb *, float); 71 | static void gverb_set_taillevel(ty_gverb *, float); 72 | 73 | /* 74 | * This FDN reverb can be made smoother by setting matrix elements at the 75 | * diagonal and near of it to zero or nearly zero. By setting diagonals to zero 76 | * means we remove the effect of the parallel comb structure from the 77 | * reverberation. A comb generates uniform impulse stream to the reverberation 78 | * impulse response, and thus it is not good. By setting near diagonal elements 79 | * to zero means we remove delay sequences having consequtive delays of the 80 | * similar lenths, when the delays are in sorted in length with respect to 81 | * matrix element index. The matrix described here could be generated by 82 | * differencing Rocchesso's circulant matrix at max diffuse value and at low 83 | * diffuse value (approaching parallel combs). 84 | * 85 | * Example 1: 86 | * Set a(k,k), for all k, equal to 0. 87 | * 88 | * Example 2: 89 | * Set a(k,k), a(k,k-1) and a(k,k+1) equal to 0. 90 | * 91 | * Example 3: The transition to zero gains could be smooth as well. 92 | * a(k,k-1) and a(k,k+1) could be 0.3, and a(k,k-2) and a(k,k+2) could 93 | * be 0.5, say. 94 | */ 95 | 96 | static __inline void gverb_fdnmatrix(float *a, float *b) 97 | { 98 | const float dl0 = a[0], dl1 = a[1], dl2 = a[2], dl3 = a[3]; 99 | 100 | b[0] = 0.5f*(+dl0 + dl1 - dl2 - dl3); 101 | b[1] = 0.5f*(+dl0 - dl1 - dl2 + dl3); 102 | b[2] = 0.5f*(-dl0 + dl1 - dl2 + dl3); 103 | b[3] = 0.5f*(+dl0 + dl1 + dl2 + dl3); 104 | } 105 | 106 | static __inline void gverb_do(ty_gverb *p, float x, float *yl, float *yr) 107 | { 108 | float z; 109 | unsigned int i; 110 | float lsum,rsum,sum,sign; 111 | 112 | if ((x != x) || fabsf(x) > 100000.0f) { 113 | x = 0.0f; 114 | } 115 | 116 | z = damper_do(p->inputdamper, x); 117 | 118 | z = diffuser_do(p->ldifs[0],z); 119 | 120 | for(i = 0; i < FDNORDER; i++) { 121 | p->u[i] = p->tapgains[i]*fixeddelay_read(p->tapdelay,p->taps[i]); 122 | } 123 | fixeddelay_write(p->tapdelay,z); 124 | 125 | for(i = 0; i < FDNORDER; i++) { 126 | p->d[i] = damper_do(p->fdndamps[i], 127 | p->fdngains[i]*fixeddelay_read(p->fdndels[i], 128 | p->fdnlens[i])); 129 | } 130 | 131 | sum = 0.0f; 132 | sign = 1.0f; 133 | for(i = 0; i < FDNORDER; i++) { 134 | sum += sign*(p->taillevel*p->d[i] + p->earlylevel*p->u[i]); 135 | sign = -sign; 136 | } 137 | sum += x*p->earlylevel; 138 | lsum = sum; 139 | rsum = sum; 140 | 141 | gverb_fdnmatrix(p->d,p->f); 142 | 143 | for(i = 0; i < FDNORDER; i++) { 144 | fixeddelay_write(p->fdndels[i],p->u[i]+p->f[i]); 145 | } 146 | 147 | lsum = diffuser_do(p->ldifs[1],lsum); 148 | lsum = diffuser_do(p->ldifs[2],lsum); 149 | lsum = diffuser_do(p->ldifs[3],lsum); 150 | rsum = diffuser_do(p->rdifs[1],rsum); 151 | rsum = diffuser_do(p->rdifs[2],rsum); 152 | rsum = diffuser_do(p->rdifs[3],rsum); 153 | 154 | *yl = lsum; 155 | *yr = rsum; 156 | } 157 | 158 | static __inline void gverb_set_roomsize(ty_gverb *p, const float a) 159 | { 160 | unsigned int i; 161 | 162 | if (a <= 1.0 || (a != a)) { 163 | p->roomsize = 1.0; 164 | } else { 165 | p->roomsize = a; 166 | } 167 | p->largestdelay = p->rate * p->roomsize * 0.00294f; 168 | 169 | p->fdnlens[0] = f_round(1.000000f*p->largestdelay); 170 | p->fdnlens[1] = f_round(0.816490f*p->largestdelay); 171 | p->fdnlens[2] = f_round(0.707100f*p->largestdelay); 172 | p->fdnlens[3] = f_round(0.632450f*p->largestdelay); 173 | for(i = 0; i < FDNORDER; i++) { 174 | p->fdngains[i] = -powf((float)p->alpha, p->fdnlens[i]); 175 | } 176 | 177 | p->taps[0] = 5+f_round(0.410f*p->largestdelay); 178 | p->taps[1] = 5+f_round(0.300f*p->largestdelay); 179 | p->taps[2] = 5+f_round(0.155f*p->largestdelay); 180 | p->taps[3] = 5+f_round(0.000f*p->largestdelay); 181 | 182 | for(i = 0; i < FDNORDER; i++) { 183 | p->tapgains[i] = powf((float)p->alpha, p->taps[i]); 184 | } 185 | 186 | } 187 | 188 | static __inline void gverb_set_revtime(ty_gverb *p,float a) 189 | { 190 | float ga,gt; 191 | double n; 192 | unsigned int i; 193 | 194 | p->revtime = a; 195 | 196 | ga = 60.0; 197 | gt = p->revtime; 198 | ga = powf(10.0f,-ga/20.0f); 199 | n = p->rate*gt; 200 | p->alpha = (double)powf(ga,1.0f/n); 201 | 202 | for(i = 0; i < FDNORDER; i++) { 203 | p->fdngains[i] = -powf((float)p->alpha, p->fdnlens[i]); 204 | } 205 | 206 | } 207 | 208 | static __inline void gverb_set_damping(ty_gverb *p,float a) 209 | { 210 | unsigned int i; 211 | 212 | p->fdndamping = a; 213 | for(i = 0; i < FDNORDER; i++) { 214 | damper_set(p->fdndamps[i],p->fdndamping); 215 | } 216 | } 217 | 218 | static __inline void gverb_set_inputbandwidth(ty_gverb *p,float a) 219 | { 220 | p->inputbandwidth = a; 221 | damper_set(p->inputdamper,1.0 - p->inputbandwidth); 222 | } 223 | 224 | static __inline void gverb_set_earlylevel(ty_gverb *p,float a) 225 | { 226 | p->earlylevel = a; 227 | } 228 | 229 | static __inline void gverb_set_taillevel(ty_gverb *p,float a) 230 | { 231 | p->taillevel = a; 232 | } 233 | 234 | #endif 235 | -------------------------------------------------------------------------------- /src/PianoRoll/PianoRollWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | #include "dsp/window.hpp" 3 | 4 | #include "../plugin.hpp" 5 | #include "PatternWidget.hpp" 6 | #include "PianoRollWidget.hpp" 7 | #include "PianoRollModule.hpp" 8 | #include "MenuItems/CancelPasteItem.hpp" 9 | #include "MenuItems/ClearNotesItem.hpp" 10 | #include "MenuItems/ClockBufferItem.hpp" 11 | #include "MenuItems/CopyMeasureItem.hpp" 12 | #include "MenuItems/CopyPatternItem.hpp" 13 | #include "MenuItems/NotesToShowItem.hpp" 14 | #include "MenuItems/PasteMeasureItem.hpp" 15 | #include "MenuItems/PastePatternItem.hpp" 16 | 17 | using namespace rack; 18 | 19 | extern Plugin* plugin; 20 | 21 | // standalone module for the module browser 22 | PianoRollModule browserModule; 23 | 24 | PianoRollWidget::PianoRollWidget(PianoRollModule *module) { 25 | setModule(module); 26 | PianoRollModule *originalmodule = module; 27 | module = this->module = module == NULL ? &browserModule : module; 28 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/PianoRoll.svg"))); 29 | 30 | addInput(createInput(Vec(50.114, 380.f-91-23.6), originalmodule, PianoRollModule::CLOCK_INPUT)); 31 | addInput(createInput(Vec(85.642, 380.f-91-23.6), originalmodule, PianoRollModule::RESET_INPUT)); 32 | addInput(createInput(Vec(121.170, 380.f-91-23.6), originalmodule, PianoRollModule::PATTERN_INPUT)); 33 | addInput(createInput(Vec(156.697, 380.f-91-23.6), originalmodule, PianoRollModule::RUN_INPUT)); 34 | addInput(createInput(Vec(192.224, 380.f-91-23.6), originalmodule, PianoRollModule::RECORD_INPUT)); 35 | 36 | addInput(createInput(Vec(421.394, 380.f-91-23.6), originalmodule, PianoRollModule::VOCT_INPUT)); 37 | addInput(createInput(Vec(456.921, 380.f-91-23.6), originalmodule, PianoRollModule::GATE_INPUT)); 38 | addInput(createInput(Vec(492.448, 380.f-91-23.6), originalmodule, PianoRollModule::RETRIGGER_INPUT)); 39 | addInput(createInput(Vec(527.976, 380.f-91-23.6), originalmodule, PianoRollModule::VELOCITY_INPUT)); 40 | 41 | addOutput(createOutput(Vec(50.114, 380.f-25.9-23.6), originalmodule, PianoRollModule::CLOCK_OUTPUT)); 42 | addOutput(createOutput(Vec(85.642, 380.f-25.9-23.6), originalmodule, PianoRollModule::RESET_OUTPUT)); 43 | addOutput(createOutput(Vec(121.170, 380.f-25.9-23.6), originalmodule, PianoRollModule::PATTERN_OUTPUT)); 44 | addOutput(createOutput(Vec(156.697, 380.f-25.9-23.6), originalmodule, PianoRollModule::RUN_OUTPUT)); 45 | addOutput(createOutput(Vec(192.224, 380.f-25.9-23.6), originalmodule, PianoRollModule::RECORD_OUTPUT)); 46 | 47 | addOutput(createOutput(Vec(421.394, 380.f-25.9-23.6), originalmodule, PianoRollModule::VOCT_OUTPUT)); 48 | addOutput(createOutput(Vec(456.921, 380.f-25.9-23.6), originalmodule, PianoRollModule::GATE_OUTPUT)); 49 | addOutput(createOutput(Vec(492.448, 380.f-25.9-23.6), originalmodule, PianoRollModule::RETRIGGER_OUTPUT)); 50 | addOutput(createOutput(Vec(527.976, 380.f-25.9-23.6), originalmodule, PianoRollModule::VELOCITY_OUTPUT)); 51 | addOutput(createOutput(Vec(563.503, 380.f-25.9-23.6), originalmodule, PianoRollModule::END_OF_PATTERN_OUTPUT)); 52 | 53 | rollAreaWidget = new RollAreaWidget(&module->patternData, &module->transport, &module->auditioner); 54 | rollAreaWidget->box = getRollArea(); 55 | addChild(rollAreaWidget); 56 | 57 | PatternWidget* patternWidget = createWidget(Vec(505.257, 380.f-224.259-125.586)); 58 | patternWidget->module = module; 59 | patternWidget->widget = this; 60 | addChild(patternWidget); 61 | 62 | initColourChange(Rect(Vec(506, 10), Vec(85, 13)), module, 0.5f, 1.f, 0.25f); 63 | } 64 | 65 | void PianoRollWidget::appendContextMenu(Menu* menu) { 66 | 67 | menu->addChild(createMenuLabel("")); 68 | menu->addChild(construct(&MenuLabel::text, "Copy / Paste")); 69 | 70 | CopyPatternItem *copyPatternItem = new CopyPatternItem(); 71 | copyPatternItem->widget = this; 72 | copyPatternItem->module = module; 73 | copyPatternItem->text = "Copy Pattern"; 74 | menu->addChild(copyPatternItem); 75 | 76 | CopyMeasureItem *copyMeasureItem = new CopyMeasureItem(); 77 | copyMeasureItem->widget = this; 78 | copyMeasureItem->module = module; 79 | copyMeasureItem->text = "Copy Measure"; 80 | menu->addChild(copyMeasureItem); 81 | 82 | switch(state) { 83 | case COPYREADY: 84 | break; 85 | case PATTERNLOADED: 86 | { 87 | PastePatternItem *pastePatternItem = new PastePatternItem(); 88 | pastePatternItem->widget = this; 89 | pastePatternItem->module = module; 90 | pastePatternItem->text = "Paste Pattern"; 91 | menu->addChild(pastePatternItem); 92 | } 93 | break; 94 | case MEASURELOADED: 95 | { 96 | PasteMeasureItem *pasteMeasureItem = new PasteMeasureItem(); 97 | pasteMeasureItem->widget = this; 98 | pasteMeasureItem->module = module; 99 | pasteMeasureItem->text = "Paste Measure"; 100 | menu->addChild(pasteMeasureItem); 101 | } 102 | break; 103 | default: 104 | state = COPYREADY; 105 | break; 106 | } 107 | 108 | menu->addChild(createMenuLabel("")); 109 | menu->addChild(new ClearNotesItem(this->module)); 110 | 111 | menu->addChild(createMenuLabel("")); 112 | menu->addChild(createMenuLabel("Notes to Show")); 113 | menu->addChild(new NotesToShowItem(this, 12)); 114 | menu->addChild(new NotesToShowItem(this, 18)); 115 | menu->addChild(new NotesToShowItem(this, 24)); 116 | menu->addChild(new NotesToShowItem(this, 36)); 117 | menu->addChild(new NotesToShowItem(this, 48)); 118 | menu->addChild(new NotesToShowItem(this, 60)); 119 | menu->addChild(createMenuLabel("")); 120 | menu->addChild(createMenuLabel("Clock Delay (samples)")); 121 | menu->addChild(new ClockBufferItem(module, 0)); 122 | menu->addChild(new ClockBufferItem(module, 1)); 123 | menu->addChild(new ClockBufferItem(module, 2)); 124 | menu->addChild(new ClockBufferItem(module, 3)); 125 | menu->addChild(new ClockBufferItem(module, 4)); 126 | menu->addChild(new ClockBufferItem(module, 5)); 127 | menu->addChild(new ClockBufferItem(module, 10)); 128 | menu->addChild(createMenuLabel("")); 129 | if (module) { 130 | menu->addChild(createBoolPtrMenuItem("Driver Mode", "", &((PianoRollModule*)module)->driverMode)); 131 | } 132 | } 133 | 134 | Rect PianoRollWidget::getRollArea() { 135 | Rect roll; 136 | roll.pos.x = 15.f; 137 | roll.pos.y = 380-365.f; 138 | roll.size.x = 480.f; 139 | roll.size.y = 220.f; 140 | return roll; 141 | } 142 | 143 | void PianoRollWidget::step() { 144 | if (module && module->state.dirty) { 145 | this->rollAreaWidget->state.lowestDisplayNote = module->state.lowestDisplayNote; 146 | this->rollAreaWidget->state.notesToShow = module->state.notesToShow; 147 | this->rollAreaWidget->state.currentMeasure = module->state.currentMeasure; 148 | this->rollAreaWidget->state.dirty = true; 149 | module->state.dirty = false; 150 | } else if (module && this->rollAreaWidget->stateNeedsSaving) { 151 | module->state.lowestDisplayNote = this->rollAreaWidget->state.lowestDisplayNote; 152 | module->state.notesToShow = this->rollAreaWidget->state.notesToShow; 153 | module->state.currentMeasure = this->rollAreaWidget->state.currentMeasure; 154 | this->rollAreaWidget->stateNeedsSaving = false; 155 | } 156 | 157 | BaseWidget::step(); 158 | } 159 | 160 | -------------------------------------------------------------------------------- /src/PianoRoll/DragModes.cpp: -------------------------------------------------------------------------------- 1 | #include "rack.hpp" 2 | #include "../PianoRoll/DragModes.hpp" 3 | #include "../PianoRoll/RollAreaWidget.hpp" 4 | #include "../PianoRoll/Auditioner.hpp" 5 | #include "../PianoRoll/PatternData.hpp" 6 | #include "../PianoRoll/Transport.hpp" 7 | 8 | static const float VELOCITY_SENSITIVITY = 0.0015f; 9 | static const float KEYBOARDDRAG_SENSITIVITY = 0.1f; 10 | 11 | PianoRollDragType::PianoRollDragType() {} 12 | 13 | PianoRollDragType::~PianoRollDragType() {} 14 | 15 | 16 | PlayPositionDragging::PlayPositionDragging(Auditioner* auditioner, UnderlyingRollAreaWidget* widget, Transport* transport): auditioner(auditioner), widget(widget), transport(transport) { 17 | setNote(Vec(0,0)); 18 | } 19 | 20 | PlayPositionDragging::~PlayPositionDragging() { 21 | auditioner->stop(); 22 | } 23 | 24 | void PlayPositionDragging::onDragMove(const rack::event::DragMove& e) { 25 | Vec mouseDelta(e.mouseDelta.div(APP->scene->rackScroll->zoomWidget->zoom)); 26 | setNote(mouseDelta); 27 | } 28 | 29 | void PlayPositionDragging::setNote(Vec mouseRel) { 30 | Vec pos(widget->lastMouseDown.x + mouseRel.x, widget->lastMouseDown.y + mouseRel.y); 31 | widget->lastMouseDown = pos; 32 | 33 | Rect roll(Vec(0,0), Vec(widget->box.size.x, widget->box.size.y)); 34 | widget->reserveKeysArea(roll); 35 | 36 | auto beatDivs = widget->getBeatDivs(roll); 37 | bool beatDivFound = false; 38 | BeatDiv cellBeatDiv; 39 | 40 | for (auto const& beatDiv: beatDivs) { 41 | if (Rect(Vec(beatDiv.pos.x, 0), Vec(beatDiv.size.x, widget->box.size.y)).isContaining(pos)) { 42 | cellBeatDiv = beatDiv; 43 | beatDivFound = true; 44 | break; 45 | } 46 | } 47 | 48 | if (beatDivFound) { 49 | transport->setMeasure(widget->state->currentMeasure); 50 | transport->setStepInMeasure(cellBeatDiv.num); 51 | auditioner->start(transport->currentStepInPattern()); 52 | } else { 53 | auditioner->stop(); 54 | } 55 | } 56 | 57 | LockMeasureDragging::LockMeasureDragging(WidgetState* state, Transport* transport) : state(state), transport(transport) { 58 | longPressStart = std::chrono::high_resolution_clock::now(); 59 | state->measureLockPressTime = 0.f; 60 | state->dirty = true; 61 | } 62 | 63 | LockMeasureDragging::~LockMeasureDragging() { 64 | state->measureLockPressTime = 0.f; 65 | state->dirty = true; 66 | } 67 | 68 | void LockMeasureDragging::onDragMove(const rack::event::DragMove &e) { 69 | auto currTime = std::chrono::high_resolution_clock::now(); 70 | double pressTime = std::chrono::duration(currTime - longPressStart).count(); 71 | state->measureLockPressTime = clamp(pressTime, 0.f, 1.f); 72 | state->dirty = true; 73 | if (pressTime >= 1.f) { 74 | 75 | if (!transport->isLocked() || (transport->currentMeasure() != state->currentMeasure)) { 76 | transport->lockMeasure(); 77 | 78 | if (transport->currentMeasure() != state->currentMeasure) { 79 | // We just locked the measure, but the play point is outside the selected measure - move the play point into the last note of the current measure 80 | transport->setMeasure(state->currentMeasure); 81 | } 82 | } else { 83 | transport->unlockMeasure(); 84 | } 85 | 86 | longPressStart = std::chrono::high_resolution_clock::now(); 87 | } 88 | } 89 | 90 | KeyboardDragging::KeyboardDragging(WidgetState* state) : state(state) { 91 | APP->window->cursorLock(); 92 | } 93 | 94 | KeyboardDragging::~KeyboardDragging() { 95 | APP->window->cursorUnlock(); 96 | } 97 | 98 | void KeyboardDragging::onDragMove(const rack::event::DragMove& e) { 99 | float speed = 1.f; 100 | float range = 1.f; 101 | 102 | Vec mouseDelta(e.mouseDelta.div(APP->scene->rackScroll->zoomWidget->zoom)); 103 | 104 | float delta = KEYBOARDDRAG_SENSITIVITY * mouseDelta.y * speed * range; 105 | if ((APP->window->getMods() & GLFW_MOD_CONTROL)) { 106 | delta /= 16.f; 107 | } 108 | 109 | offset += delta; 110 | 111 | while (offset >= 1.f) { 112 | state->lowestDisplayNote = clamp(state->lowestDisplayNote + 1, -1 * 12, 8 * 12); 113 | state->dirty = true; 114 | offset -= 1; 115 | } 116 | 117 | while (offset <= -1.f) { 118 | state->lowestDisplayNote = clamp(state->lowestDisplayNote - 1, -1 * 12, 8 * 12); 119 | state->dirty = true; 120 | offset += 1; 121 | } 122 | } 123 | 124 | NotePaintDragging::NotePaintDragging(UnderlyingRollAreaWidget* widget, PatternData* patternData, Transport* transport, Auditioner* auditioner) : widget(widget), patternData(patternData), transport(transport), auditioner(auditioner) { 125 | Vec pos = widget->lastMouseDown; 126 | 127 | pitchLocked = false; 128 | 129 | std::tuple cell = widget->findCell(pos); 130 | if (!std::get<0>(cell)) { 131 | return; 132 | } 133 | 134 | int beatDiv = std::get<1>(cell).num; 135 | int pitch = std::get<2>(cell).pitch(); 136 | 137 | bool wasAlreadyActive = patternData->isStepActive(transport->currentPattern(), widget->state->currentMeasure, beatDiv); 138 | bool wasAlreadyRetriggered = patternData->isStepRetriggered(transport->currentPattern(), widget->state->currentMeasure, beatDiv); 139 | 140 | retriggerBeatDiv = !wasAlreadyActive || wasAlreadyRetriggered ? beatDiv : -1; 141 | 142 | if (pitch == patternData->getStepPitch(transport->currentPattern(), widget->state->currentMeasure, beatDiv)) { 143 | makeStepsActive = !patternData->isStepActive(transport->currentPattern(), widget->state->currentMeasure, beatDiv); 144 | } else { 145 | makeStepsActive = true; 146 | } 147 | 148 | APP->history->push(new PatternData::PatternAction("note painting", patternData->moduleId, transport->currentPattern(), *patternData)); 149 | } 150 | 151 | NotePaintDragging::~NotePaintDragging() { 152 | if (makeStepsActive) { 153 | auditioner->stop(); 154 | } 155 | } 156 | 157 | void NotePaintDragging::onDragMove(const rack::event::DragMove& e) { 158 | Vec mouseDelta(e.mouseDelta.div(APP->scene->rackScroll->zoomWidget->zoom)); 159 | 160 | Vec pos(widget->lastMouseDown.x + mouseDelta.x, widget->lastMouseDown.y + mouseDelta.y); 161 | widget->lastMouseDown = pos; 162 | 163 | std::tuple cell = widget->findCell(pos); 164 | if (!std::get<0>(cell)) { 165 | auditioner->stop(); 166 | return; 167 | } 168 | 169 | int beatDiv = std::get<1>(cell).num; 170 | int pitch = std::get<2>(cell).pitch(); 171 | 172 | if (lastDragBeatDiv != beatDiv || lastDragPitch != pitch) { 173 | if (lastDragBeatDiv != -1000 && lastDragPitch != -1000 && lastDragBeatDiv != beatDiv) { 174 | pitchLocked = true; 175 | } 176 | 177 | if (pitchLocked) { 178 | pitch = lastDragPitch; 179 | } else { 180 | lastDragPitch = pitch; 181 | } 182 | 183 | lastDragBeatDiv = beatDiv; 184 | 185 | if (makeStepsActive) { 186 | bool wasAlreadyActive = patternData->isStepActive(transport->currentPattern(), widget->state->currentMeasure, beatDiv); 187 | 188 | patternData->setStepActive(transport->currentPattern(), widget->state->currentMeasure, beatDiv, true); 189 | patternData->setStepPitch(transport->currentPattern(), widget->state->currentMeasure, beatDiv, pitch); 190 | 191 | if (beatDiv < retriggerBeatDiv) { 192 | patternData->setStepRetrigger(transport->currentPattern(), widget->state->currentMeasure, retriggerBeatDiv, false); 193 | retriggerBeatDiv = beatDiv; 194 | } 195 | 196 | patternData->setStepRetrigger(transport->currentPattern(), widget->state->currentMeasure, beatDiv, beatDiv == retriggerBeatDiv); 197 | 198 | if (!wasAlreadyActive) { 199 | patternData->setStepVelocity(transport->currentPattern(), widget->state->currentMeasure, beatDiv, 0.75); 200 | } 201 | 202 | patternData->adjustVelocity(transport->currentPattern(), widget->state->currentMeasure, beatDiv, 0.f); 203 | 204 | auditioner->start(beatDiv + (patternData->getStepsPerMeasure(transport->currentPattern()) * widget->state->currentMeasure)); 205 | auditioner->retrigger(); 206 | } else { 207 | patternData->setStepActive(transport->currentPattern(), widget->state->currentMeasure, beatDiv, false); 208 | patternData->setStepRetrigger(transport->currentPattern(), widget->state->currentMeasure, beatDiv, false); 209 | } 210 | }; 211 | } 212 | 213 | VelocityDragging::VelocityDragging(UnderlyingRollAreaWidget* widget, PatternData* patternData, Transport* transport, WidgetState* state, int pattern, int measure, int division) 214 | : widget(widget), 215 | patternData(patternData), 216 | transport(transport), 217 | state(state), 218 | pattern(pattern), 219 | measure(measure), 220 | division(division) { 221 | APP->window->cursorLock(); 222 | 223 | Rect roll(Vec(0,0), Vec(widget->box.size.x, widget->box.size.y)); 224 | widget->reserveKeysArea(roll); 225 | 226 | roll.size.y = roll.size.y / 2.f; 227 | showLow = roll.isContaining(widget->lastMouseDown); 228 | 229 | APP->history->push(new PatternData::PatternAction("change velocity", patternData->moduleId, transport->currentPattern(), *patternData)); 230 | } 231 | 232 | VelocityDragging::~VelocityDragging() { 233 | APP->window->cursorUnlock(); 234 | state->displayVelocityHigh = -1; 235 | state->displayVelocityLow = -1; 236 | state->dirty = true; 237 | } 238 | 239 | void VelocityDragging::onDragMove(const rack::event::DragMove& e) { 240 | Vec pos(widget->lastMouseDown.x + e.mouseDelta.x, widget->lastMouseDown.y + e.mouseDelta.y); 241 | widget->lastMouseDown = pos; 242 | 243 | float speed = 1.f; 244 | float range = 1.f; 245 | float delta = VELOCITY_SENSITIVITY * -e.mouseDelta.y * speed * range; 246 | if ((APP->window->getMods() & GLFW_MOD_CONTROL)) { 247 | delta /= 16.f; 248 | } 249 | 250 | float newVelocity = patternData->adjustVelocity(transport->currentPattern(), measure, division, delta); 251 | if (showLow) { 252 | state->displayVelocityHigh = -1; 253 | state->displayVelocityLow = newVelocity; 254 | } else { 255 | state->displayVelocityHigh = newVelocity; 256 | state->displayVelocityLow = -1; 257 | } 258 | state->dirty = true; 259 | } 260 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # RCM plugins 3 | 4 | ![pack](/res/screenshot.png?raw=true "pack") 5 | 6 | ## Piano Roll 7 | 8 | A monophonic quantized sequencer. With `1v/oct`, `gate`, `retrigger` & `velocity` outputs. 64 patterns with up to 16 measures per pattern, up to 16 beats per measure and up to 16 divisions per beat. `EOP` will trigger when the last note in the pattern has been triggered. Patterns always loop (currently). 9 | 10 | #### Controls 11 | 12 | * Left click a cell to activate that note. 13 | * Right click an active note to cause it to retrigger. 14 | * Hold down shift then drag a note up & down to alter the velocity, when dragging, press ctrl for more fine-grained control. 15 | * Use the panel on the right to alter the current pattern and its make-up. 16 | * Send a pulse to clock-in to advance each step (no internal clock). 17 | * Send a pulse to reset to prepare to play the current pattern from the start (plays 1st note on receiving first clock). 18 | * Click the area above the notes to set the play position. Triggers the notes at that position. You can drag across. 19 | * CV input for pattern selection is based on 1V/oct pitch information. Send C4 for first pattern, C#4 for second, D4 for third, etc. 20 | * Click the area below the notes to switch the current measure. 21 | * Hold down a measure for 1 second to lock that measure. Playing will now loop around that measure only. 22 | * Hold down in the measure area for 1 second to unlock measures again. 23 | 24 | #### Clock sync - "Run" input 25 | 26 | The following inputs are generally for clock synchronisation: `clk`, `reset`, `run`. 27 | 28 | Note that the `run` input will toggle the run state whenever it receives a trigger - it doesn't actually know if the clock is running or not. 29 | 30 | The module defaults to the "Running" state. If your clock is paused when you hook it up, you will need to manually click the "Running" indicator to switch it into "Paused" mode so that it matches the clock. 31 | 32 | When the module is "Paused", `clk` inputs are ignored. 33 | 34 | #### Sequencing Piano Roll - Driver Mode 35 | 36 | Driving a piano roll's `ptrn` from the `1v/oct` output of another piano roll is one way to program a pattern sequence. If you plug the `eop` output from the driven piano roll into the `clk` input of the driving piano roll, when the pattern finishes playing, the driving piano roll steps to the next step and selects the next pattern in the driven piano roll. 37 | 38 | This leads to a startup issue - you need to have the driving module choose the first step of the pattern immediately. If you wait for `eop`, then you're playing whatever pattern was previously selected in the piano roll until it finishes and *then* it selects the first module. 39 | 40 | To address this, use `Driver Mode` on the pattern sequencer, by choosing it in the right click menu. This will ensure that the first step is selected as soon as the `run` or `reset` signals are received. 41 | 42 | ![driving](/res/screenshot-pianoroll.png?raw=true "driving") 43 | 44 | Note also how the `reset` and `run` signals have been chained through the driver module, this avoids timing issues even when the `clk` input is direct in this way. No clock delays are necessary when setup this way. 45 | 46 | #### Right click menu 47 | 48 | * Patterns and measures can be copied and pasted within the same module (not between modules). 49 | * All notes in the current pattern can be deleted, letting you start fresh. 50 | * You choose how many notes to show, from 1 to 5 octaves. (actually shows 1 extra note so you can more easily see which octaves are involved). 51 | * The clock input can be delayed by a few samples if you have timing issues with switching patterns. 52 | 53 | #### Clock Delay & Synchronization 54 | 55 | Despite my best efforts it can still be hard to synchronise piano rolls correctly, especially when changing patterns. 56 | 57 | The crux of the issue is this: If your piano roll is playing the last note of pattern, the next `clk` trigger will roll it around to play the first note of the pattern again. If the `ptrn` signal changes after that - well you've already played that note. The pattern will change but whatever piano roll does, it would sound wrong. If it changed to the correct pitch of the new pattern you'd get a "blip". As it is, it keeps playing the wrong note. 58 | 59 | The underlying cause is if your `ptrn` signal route is longer than your `clk` route, the `ptrn` signal will arrive after the `clk`. You can accomodate for this with Piano Roll in the right click menu by choosing `Clock Delay` and the number of samples you wish to delay by. You can choose up to 10 here. If your delay is >10 samples you probably need to address the chain to synchronise the signal changes better somehow. 60 | 61 | Be aware of modules that add slew limiting. They don't always advertise this fact. Slew limiting has the effect of making clock signals appear delayed as they ramp up and down over time, rather than immediately. This is good for de-clicking audio, bad for synchronising signals. VCV's SS-1 and SS-2 modules have slew limiters in them, meaning that if you're trying to use them as clock dividers, you may hit synchronisation issues that are larger than the tuning features of piano roll can accomodate. 62 | 63 | #### Recording 64 | 65 | You can record pitch information from a source (eg, midi input) by connecting the `1v/oct`, `gate`, `rtrg` and `vel` sources to the inputs on the right hand side (`rtrg` and `vel` inputs are optional for this). 66 | 67 | If you send a trigger to the `rec` input, the module will go into "Prerecord" mode. Once the play position wraps around to the first position in the pattern (or measure, if you've locked the current measure), then recording will start. 68 | 69 | Send a second trigger to the `rec` input to cancel recording or the prerecord mode. 70 | 71 | Once the end of the pattern has been reached, the module will automatically drop out of recording mode and start playing what has been recorded. 72 | 73 | Remember your clock input can be any source, and each received trigger moves forward one division, use a manual trigger source for more fine gained control over what you're recording. 74 | 75 | When your inputs are set up, their values are mirrored to the outputs so that you can hear what you're playing. The mirrored outputs are *not* quantised - they are just pass throughs. 76 | 77 | The recording inputs can have other uses. For example, you could have a blank pattern in your song and use the inputs to improvise that section of the song using the same instrument that was being sequenced by the other patterns. 78 | 79 | 80 | #### Chaining 81 | 82 | Piano Rolls can be chained together for chords or other effects. The inputs on the left (`clk`, `reset`, `ptrn`, `run`, `rec`) are mirrored to the outputs below. 83 | 84 | ## Sync 85 | 86 | Sync is a simplified and shrunk version of SEQ Adapter (see below). 87 | Any incoming `reset` triggers are held until the next `clk` input unless the current `clk` input is high, in which case the `reset` is played immediately. 88 | 89 | This should do a better job of keeping sequencers in sync with resets even while running. 90 | 91 | Sometimes it's necessary to ensure that only one of the `clk` or `reset` outputs is high at the same time (for example, when driving an SS-1 or SS-2 module). To enable this, enable the `No CLK on Reset` option in the right click menu. 92 | 93 | 94 | ## SEQ Adapter 95 | 96 | This module fixes the behaviour of some sequencers that skip or fail to sound the first step of their sequence when their clock is started after a reset. 97 | 98 | Simple sequencers (like SEQ-3) normally skip the first step in their sequence when you stop the clock, reset, then start the clock again. This can fail to sound the first step and possibly put the sequencer out of sync with other modules in the patch. 99 | 100 | The `SEQ Adapter` takes the `run`, `reset` and `clock` outputs from the clock module. It then creates its own `reset` and `clock` outputs which are used by the simple sequencers. These are timed in such a way that the first steps are sounded by the sequencers and they are kept in sync with the rest of the patch. 101 | 102 | The `SEQ Adapter` works by detecting when a reset has been triggered when the clock isn't running. When that happens, instead of forwarding the reset signal, it arms the module. When armed, the next `clock` signal received gets duplicated to the `reset` output. This has the effect that when you start the clock, the module is reset to the start of the sequence with the clock active. For most modules, this means that we start playing on the first step. SEQ-3 in this case will output the first gate. Hora drum sequencer will play the first drum step, etc. 103 | 104 | This does rely on the sequencers handling simultaneous `reset` and `clock` inputs correctly, so your mileage may still vary. 105 | 106 | Resetting while the patch is running should work exactly as it does now - the `reset` and `clock` inputs are simply duplicated to the outputs. 107 | 108 | With Impromptu's Clocked module, it actually works slighly better if the `Outputs reset high when not running` option is turned off. This will ensure that SEQ-3 will sound its gate, whereas it might sometimes miss it if this option is turned on (it's on by default). 109 | 110 | 111 | ## Reverb 112 | 113 | Based on the GVerb GPL'd reverb code. 114 | 115 | This is a dirty, messy and unstable reverb. Makes for some interesting, if not repeatable effects. 116 | The reset button is there because it often gets itself into a state that just needs you to stop everything and start again. 117 | Reset only resets the engine, it doesn't reset any of the knob values. 118 | 119 | GVerb is a mono in, stereo out reverb. I'm emulating stereo in by running 2 engines and mixing the output. 120 | The Spread feature cannot be modified without recreating the engine, so it's fixed to 90 degrees and uses mixing to simulate a spread effect. 121 | 122 | The output is normalised using an internal envelope following, because the output gets crazy high voltages otherwise. This follower can generate its own effects at times when things are out of hand. 123 | 124 | ## Interface 16 125 | 126 | **Removed in v2** 127 | 128 | A reskinned version of the Core Audio 16 interface. Included solely for backward compatibility with pre v1.0 patches. 129 | 130 | ## Duck 131 | 132 | A basic audio ducking module. Works in either stereo or mono mode. 133 | Audio in the "over" channels are prioritised above the "under" channel. 134 | The amount of priority and the recovery rate once the "over" channel goes silent are configurable. 135 | -------------------------------------------------------------------------------- /res/kisscc0-astronomy-light-darkness-universe-celestial-event-variety-of-musical-notes-silhouette.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 10 | 17 | 21 | 33 | 40 | 48 | 53 | 58 | 61 | 66 | 70 | 75 | 88 | 93 | 99 | 100 | -------------------------------------------------------------------------------- /src/PianoRoll/PatternWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "PatternWidget.hpp" 2 | #include "PianoRollModule.hpp" 3 | #include "PianoRollWidget.hpp" 4 | #include "../plugin.hpp" 5 | 6 | struct ChangePatternAction : history::ModuleAction { 7 | int undoPattern; 8 | int redoPattern; 9 | int expectedUndoPattern; 10 | 11 | ChangePatternAction(std::string name, int moduleId, int previousPattern, int nextPattern) { 12 | this->name = name; 13 | this->moduleId = moduleId; 14 | 15 | expectedUndoPattern = nextPattern; 16 | undoPattern = previousPattern; 17 | } 18 | 19 | void undo() override { 20 | app::ModuleWidget *mw = APP->scene->rack->getModule(moduleId); 21 | assert(mw); 22 | PianoRollModule *module = dynamic_cast(mw->module); 23 | assert(module); 24 | 25 | int currentPattern = module->transport.currentPattern(); 26 | if (currentPattern == expectedUndoPattern) { 27 | module->transport.setPattern(undoPattern); 28 | redoPattern = currentPattern; 29 | expectedUndoPattern = undoPattern; 30 | } else { 31 | // Disable redo 32 | expectedUndoPattern = -1; 33 | } 34 | } 35 | 36 | void redo() override { 37 | app::ModuleWidget *mw = APP->scene->rack->getModule(moduleId); 38 | assert(mw); 39 | PianoRollModule *module = dynamic_cast(mw->module); 40 | assert(module); 41 | 42 | int currentPattern = module->transport.currentPattern(); 43 | if (currentPattern == expectedUndoPattern) { 44 | module->transport.setPattern(redoPattern); 45 | undoPattern = currentPattern; 46 | expectedUndoPattern = redoPattern; 47 | } else { 48 | // Disable redo 49 | expectedUndoPattern = -1; 50 | } 51 | } 52 | }; 53 | 54 | struct PatternItem : MenuItem { 55 | PatternWidget *widget = NULL; 56 | int pattern; 57 | void onAction(const event::Action &e) override { 58 | int previousPattern = widget->module->transport.currentPattern(); 59 | widget->module->transport.setPattern(pattern); 60 | APP->history->push(new ChangePatternAction("change pattern", widget->module->patternData.moduleId, previousPattern, widget->module->transport.currentPattern())); 61 | } 62 | }; 63 | 64 | struct PatternChoice : LedDisplayChoice { 65 | PatternWidget *widget = NULL; 66 | 67 | void onAction(const event::Action &e) override { 68 | if (widget->module->inputs[PianoRollModule::PATTERN_INPUT].getChannels() == 0) { 69 | Vec pos = APP->scene->rack->getMousePos().minus(widget->widget->box.pos).minus(widget->box.pos); 70 | 71 | if (pos.x < 20) { 72 | int previousPattern = widget->module->transport.currentPattern(); 73 | widget->module->transport.advancePattern(-1); 74 | APP->history->push(new ChangePatternAction("change pattern", widget->module->patternData.moduleId, previousPattern, widget->module->transport.currentPattern())); 75 | } else if (pos.x > 67) { 76 | int previousPattern = widget->module->transport.currentPattern(); 77 | widget->module->transport.advancePattern(1); 78 | APP->history->push(new ChangePatternAction("change pattern", widget->module->patternData.moduleId, previousPattern, widget->module->transport.currentPattern())); 79 | } else { 80 | Menu *menu = createMenu(); 81 | menu->addChild(construct(&MenuLabel::text, "Pattern")); 82 | 83 | for (int i = 0; i < 64; i++) { 84 | PatternItem *item = new PatternItem(); 85 | item->widget = widget; 86 | item->pattern = i; 87 | item->text = stringf("%d/64", i+1); 88 | item->rightText = CHECKMARK(item->pattern == widget->module->transport.currentPattern()); 89 | menu->addChild(item); 90 | } 91 | } 92 | } 93 | } 94 | void step() override { 95 | text = stringf("- Ptrn %02d +", widget->module->transport.currentPattern() + 1); 96 | } 97 | }; 98 | 99 | struct MeasuresItem : MenuItem { 100 | PatternWidget *widget = NULL; 101 | int measures; 102 | void onAction(const event::Action &e) override { 103 | APP->history->push(new PatternData::PatternAction("set measures", widget->module->patternData.moduleId, widget->module->transport.currentPattern(), widget->module->patternData)); 104 | widget->module->patternData.setMeasures(widget->module->transport.currentPattern(), measures); 105 | } 106 | }; 107 | 108 | struct MeasuresChoice : LedDisplayChoice { 109 | PatternWidget *widget = NULL; 110 | 111 | void onAction(const event::Action &e) override { 112 | Menu *menu = createMenu(); 113 | menu->addChild(construct(&MenuLabel::text, "Measures")); 114 | 115 | for (int i = 1; i <= 16; i++) { 116 | MeasuresItem *item = new MeasuresItem(); 117 | item->widget = widget; 118 | item->measures = i; 119 | item->text = stringf("%d measures", i); 120 | item->rightText = CHECKMARK(item->measures == widget->module->patternData.getMeasures(widget->module->transport.currentPattern())); 121 | menu->addChild(item); 122 | } 123 | } 124 | void step() override { 125 | text = stringf("Measures %d", widget->module->patternData.getMeasures(widget->module->transport.currentPattern())); 126 | } 127 | }; 128 | 129 | struct BeatsPerMeasureItem : MenuItem { 130 | PatternWidget *widget = NULL; 131 | int beatsPerMeasure; 132 | void onAction(const event::Action &e) override { 133 | APP->history->push(new PatternData::PatternAction("set beats", widget->module->patternData.moduleId, widget->module->transport.currentPattern(), widget->module->patternData)); 134 | widget->module->patternData.setBeatsPerMeasure(widget->module->transport.currentPattern(), beatsPerMeasure); 135 | } 136 | }; 137 | 138 | struct BeatsPerMeasureChoice : LedDisplayChoice { 139 | PatternWidget *widget = NULL; 140 | 141 | void onAction(const event::Action &e) override { 142 | Menu *menu = createMenu(); 143 | menu->addChild(construct(&MenuLabel::text, "Beats Per Measure")); 144 | 145 | for (int i = 1; i <= 16; i++) { 146 | BeatsPerMeasureItem *item = new BeatsPerMeasureItem(); 147 | item->widget = widget; 148 | item->beatsPerMeasure = i; 149 | item->text = stringf("%d beats", i); 150 | item->rightText = CHECKMARK(item->beatsPerMeasure == widget->module->patternData.getBeatsPerMeasure(widget->module->transport.currentPattern())); 151 | menu->addChild(item); 152 | } 153 | } 154 | void step() override { 155 | text = stringf("%d", widget->module->patternData.getBeatsPerMeasure(widget->module->transport.currentPattern())); 156 | } 157 | }; 158 | 159 | struct DivisionsPerBeatItem : MenuItem { 160 | PatternWidget *widget = NULL; 161 | int divisionsPerBeat; 162 | void onAction(const event::Action &e) override { 163 | APP->history->push(new PatternData::PatternAction("set divisions", widget->module->patternData.moduleId, widget->module->transport.currentPattern(), widget->module->patternData)); 164 | widget->module->patternData.setDivisionsPerBeat(widget->module->transport.currentPattern(), divisionsPerBeat); 165 | } 166 | }; 167 | 168 | struct DivisionsPerBeatChoice : LedDisplayChoice { 169 | PatternWidget *widget = NULL; 170 | 171 | void onAction(const event::Action &e) override { 172 | Menu *menu = createMenu(); 173 | menu->addChild(construct(&MenuLabel::text, "Divisions Per Beat")); 174 | 175 | for (int i = 1; i <= 16; i++) { 176 | DivisionsPerBeatItem *item = new DivisionsPerBeatItem(); 177 | item->widget = widget; 178 | item->divisionsPerBeat = i; 179 | item->text = stringf("%d divisions", i); 180 | item->rightText = CHECKMARK(item->divisionsPerBeat == widget->module->patternData.getDivisionsPerBeat(widget->module->transport.currentPattern())); 181 | menu->addChild(item); 182 | } 183 | } 184 | void step() override { 185 | text = stringf("%d", widget->module->patternData.getDivisionsPerBeat(widget->module->transport.currentPattern())); 186 | } 187 | }; 188 | 189 | struct SequenceRunningChoice : LedDisplayChoice { 190 | PatternWidget *widget = NULL; 191 | 192 | void onAction(const event::Action &e) override { 193 | widget->module->transport.toggleRun(); 194 | } 195 | void step() override { 196 | std::string displayText; 197 | if (widget->module->transport.isRunning()) { 198 | if (widget->module->transport.isRecording()) { 199 | displayText += "Recording"; 200 | } else if (widget->module->transport.isPendingRecording()) { 201 | displayText += "Prerecord"; 202 | } else { 203 | displayText += "Running"; 204 | } 205 | } else { 206 | displayText += "Paused"; 207 | 208 | if (widget->module->transport.isRecording()) { 209 | displayText += " (rec)"; 210 | } 211 | 212 | if (widget->module->transport.isPendingRecording()) { 213 | displayText += " (pre)"; 214 | } 215 | } 216 | 217 | text = displayText; 218 | } 219 | }; 220 | 221 | 222 | PatternWidget::PatternWidget() { 223 | 224 | // measuresChoice 225 | // measuresSeparator 226 | // beatsPerMeasureChoice 227 | // beatsPerMeasureSeparator 228 | // divisionsPerBeatChoice 229 | // divisionsPerBeatSeparator 230 | // tripletsChoice 231 | 232 | Vec pos = Vec(); 233 | 234 | PatternChoice *patternChoice = createWidget(pos); 235 | patternChoice->widget = this; 236 | addChild(patternChoice); 237 | pos = patternChoice->box.getBottomLeft(); 238 | this->patternChoice = patternChoice; 239 | 240 | this->patternSeparator = createWidget(pos); 241 | addChild(this->patternSeparator); 242 | 243 | MeasuresChoice *measuresChoice = createWidget(pos); 244 | measuresChoice->widget = this; 245 | addChild(measuresChoice); 246 | pos = measuresChoice->box.getBottomLeft(); 247 | this->measuresChoice = measuresChoice; 248 | 249 | this->measuresSeparator = createWidget(pos); 250 | addChild(this->measuresSeparator); 251 | 252 | BeatsPerMeasureChoice *beatsPerMeasureChoice = createWidget(pos); 253 | beatsPerMeasureChoice->widget = this; 254 | addChild(beatsPerMeasureChoice); 255 | this->beatsPerMeasureChoice = beatsPerMeasureChoice; 256 | 257 | this->beatsPerMeasureSeparator = createWidget(pos); 258 | this->beatsPerMeasureSeparator->box.size.y = this->beatsPerMeasureChoice->box.size.y; 259 | addChild(this->beatsPerMeasureSeparator); 260 | 261 | DivisionsPerBeatChoice *divisionsPerBeatChoice = createWidget(pos); 262 | divisionsPerBeatChoice->widget = this; 263 | addChild(divisionsPerBeatChoice); 264 | this->divisionsPerBeatChoice = divisionsPerBeatChoice; 265 | pos = divisionsPerBeatChoice->box.getBottomLeft(); 266 | 267 | this->divisionsPerBeatSeparator = createWidget(pos); 268 | addChild(this->divisionsPerBeatSeparator); 269 | 270 | SequenceRunningChoice *sequenceRunningChoice = createWidget(pos); 271 | sequenceRunningChoice->widget = this; 272 | addChild(sequenceRunningChoice); 273 | this->sequenceRunningChoice = sequenceRunningChoice; 274 | pos = sequenceRunningChoice->box.getBottomLeft(); 275 | 276 | box.size = Vec(86.863, pos.y); 277 | 278 | this->patternChoice->box.size.x = box.size.x; 279 | this->patternSeparator->box.size.x = box.size.x; 280 | this->measuresChoice->box.size.x = box.size.x; 281 | this->measuresSeparator->box.size.x = box.size.x; 282 | this->beatsPerMeasureChoice->box.size.x = box.size.x / 2; 283 | this->beatsPerMeasureSeparator->box.pos.x = box.size.x / 2; 284 | this->divisionsPerBeatChoice->box.pos.x = box.size.x / 2; 285 | this->divisionsPerBeatChoice->box.size.x = box.size.x / 2; 286 | this->divisionsPerBeatSeparator->box.size.x = box.size.x; 287 | this->sequenceRunningChoice->box.size.x = box.size.x; 288 | } 289 | -------------------------------------------------------------------------------- /src/GVerbModule.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "BaseWidget.hpp" 3 | 4 | extern "C" 5 | { 6 | #include "gverb.h" 7 | } 8 | 9 | using namespace std; 10 | 11 | struct Follower { 12 | float level = 0.f; 13 | 14 | void step(float* left, float* right) { 15 | auto value = max(abs(*left), abs(*right)); 16 | 17 | if (value >= level) { 18 | level = value; 19 | } else { 20 | level -= (level - value) * 0.001; 21 | } 22 | 23 | if (level > 10.f) { 24 | *left /= (level / 10.f); 25 | *right /= (level / 10.f); 26 | } 27 | } 28 | }; 29 | 30 | struct GVerbModule : BaseModule { 31 | enum ParamIds { 32 | ROOM_SIZE_PARAM, 33 | REV_TIME_PARAM, 34 | DAMPING_PARAM, 35 | SPREAD_PARAM, 36 | BANDWIDTH_PARAM, 37 | EARLY_LEVEL_PARAM, 38 | TAIL_LEVEL_PARAM, 39 | MIX_PARAM, 40 | RESET_PARAM, 41 | ROOM_SIZE_POT_PARAM, 42 | DAMPING_POT_PARAM, 43 | REV_TIME_POT_PARAM, 44 | BANDWIDTH_POT_PARAM, 45 | EARLY_LEVEL_POT_PARAM, 46 | TAIL_LEVEL_POT_PARAM, 47 | MIX_POT_PARAM, 48 | SPREAD_POT_PARAM, 49 | NUM_PARAMS 50 | }; 51 | enum InputIds { 52 | LEFT_AUDIO, 53 | RIGHT_AUDIO, 54 | ROOM_SIZE_INPUT, 55 | DAMPING_INPUT, 56 | REV_TIME_INPUT, 57 | BANDWIDTH_INPUT, 58 | EARLY_LEVEL_INPUT, 59 | TAIL_LEVEL_INPUT, 60 | MIX_INPUT, 61 | SPREAD_INPUT, 62 | RESET_INPUT, 63 | NUM_INPUTS 64 | }; 65 | enum OutputIds { 66 | LEFT_OUTPUT, 67 | RIGHT_OUTPUT, 68 | NUM_OUTPUTS 69 | }; 70 | enum LightIds { 71 | BLINK_LIGHT, 72 | NUM_LIGHTS 73 | }; 74 | 75 | ty_gverb* gverbL = NULL; 76 | ty_gverb* gverbR = NULL; 77 | 78 | GVerbModule() : BaseModule() { 79 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 80 | 81 | configParam(ROOM_SIZE_PARAM, 2.0, 300.0, 20.0); 82 | configParam(DAMPING_PARAM, 0.0, 1.0, 0.98); 83 | 84 | configParam(REV_TIME_PARAM, 0.0, 10.0, 1.0); 85 | configParam(BANDWIDTH_PARAM, 0.0, 1.0, 0.01); 86 | configParam(EARLY_LEVEL_PARAM, 0.0, 1.0, 0.8); 87 | configParam(TAIL_LEVEL_PARAM, 0.0, 1.0, 0.5); 88 | 89 | configParam(MIX_PARAM, 0.0, 1.0, 0.4); 90 | configParam(SPREAD_PARAM, 0.0, 1.0, 1.0); 91 | configParam(RESET_PARAM, 0.0, 1.0, 0.0); 92 | 93 | configParam(ROOM_SIZE_POT_PARAM, -1.f, 1.f, 0.f); 94 | configParam(DAMPING_POT_PARAM, -1.f, 1.f, 0.f); 95 | configParam(REV_TIME_POT_PARAM, -1.f, 1.f, 0.f); 96 | configParam(BANDWIDTH_POT_PARAM, -1.f, 1.f, 0.f); 97 | configParam(EARLY_LEVEL_POT_PARAM, -1.f, 1.f, 0.f); 98 | configParam(TAIL_LEVEL_POT_PARAM, -1.f, 1.f, 0.f); 99 | configParam(MIX_POT_PARAM, -1.f, 1.f, 0.f); 100 | configParam(SPREAD_POT_PARAM, -1.f, 1.f, 0.f); 101 | } 102 | void onSampleRateChange() override; 103 | void process(const ProcessArgs &args) override; 104 | void disposeGverbL(); 105 | void disposeGverbR(); 106 | 107 | float p_frequency = 0.f; 108 | float p_room_size = 0.f; 109 | float p_rev_time = 0.f; 110 | float p_damping = 0.f; 111 | float p_bandwidth = 0.f; 112 | float p_early_level = 0.f; 113 | float p_tail_level = 0.f; 114 | 115 | Follower follower; 116 | 117 | dsp::SchmittTrigger resetTrigger; 118 | 119 | float getParam(ParamIds param, InputIds mod, ParamIds trim, float min, float max); 120 | void handleParam(float value, float* store, void (*change)(ty_gverb*,float)); 121 | 122 | // For more advanced Module features, read Rack's engine.hpp header file 123 | // - dataToJson, dataFromJson: serialization of internal data 124 | // - onSampleRateChange: event triggered by a change of sample rate 125 | // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu 126 | }; 127 | 128 | void GVerbModule::disposeGverbL() { 129 | if (gverbL != NULL) { 130 | gverb_free(gverbL); 131 | gverbL = NULL; 132 | } 133 | } 134 | 135 | void GVerbModule::disposeGverbR() { 136 | if (gverbR != NULL) { 137 | gverb_free(gverbR); 138 | gverbR = NULL; 139 | } 140 | } 141 | 142 | float GVerbModule::getParam(ParamIds param, InputIds mod, ParamIds trim, float min, float max) { 143 | return clampSafe(params[param].value + (((clampSafe(inputs[mod].getVoltage(), -10.f, 10.f)/10) * max) * params[trim].value), min, max); 144 | } 145 | 146 | void GVerbModule::handleParam(float value, float* store, void (*change)(ty_gverb*,float)) { 147 | if (*store != value) { 148 | if (gverbL != NULL) { 149 | change(gverbL, value); 150 | } 151 | if (gverbR != NULL) { 152 | change(gverbR, value); 153 | } 154 | *store = value; 155 | } 156 | } 157 | 158 | void GVerbModule::onSampleRateChange() { 159 | disposeGverbL(); 160 | disposeGverbR(); 161 | } 162 | 163 | void GVerbModule::process(const rack::Module::ProcessArgs &args) { 164 | auto leftAudioIn = inputs[LEFT_AUDIO].getVoltageSum(); 165 | auto rightAudioIn = inputs[RIGHT_AUDIO].getVoltageSum(); 166 | 167 | auto reset = max(params[RESET_PARAM].value, inputs[RESET_INPUT].getVoltage()); 168 | auto mix = getParam(MIX_PARAM, MIX_INPUT, MIX_POT_PARAM, 0.f, 1.f); 169 | 170 | if (resetTrigger.process(reset)) { 171 | disposeGverbL(); 172 | disposeGverbR(); 173 | } 174 | 175 | if (gverbL != NULL && inputs[LEFT_AUDIO].getChannels() == 0) { 176 | disposeGverbL(); 177 | } 178 | 179 | if (gverbR != NULL && inputs[RIGHT_AUDIO].getChannels() == 0) { 180 | disposeGverbR(); 181 | } 182 | 183 | if (gverbL == NULL) { 184 | if (inputs[LEFT_AUDIO].getChannels() > 0) { 185 | gverbL = gverb_new( 186 | args.sampleRate, // freq 187 | 300, // max room size 188 | getParam(ROOM_SIZE_PARAM, ROOM_SIZE_INPUT, ROOM_SIZE_POT_PARAM, 2.f, 300.f), // room size 189 | getParam(REV_TIME_PARAM, REV_TIME_INPUT, REV_TIME_POT_PARAM, 0.f, 10000.f), // revtime 190 | getParam(DAMPING_PARAM, DAMPING_INPUT, DAMPING_POT_PARAM, 0.f, 1.f), // damping 191 | 90.0, // spread 192 | getParam(BANDWIDTH_PARAM, BANDWIDTH_INPUT, BANDWIDTH_POT_PARAM, 0.f, 1.f), // input bandwidth 193 | getParam(EARLY_LEVEL_PARAM, EARLY_LEVEL_INPUT, EARLY_LEVEL_POT_PARAM, 0.f, 1.f), // early level 194 | getParam(TAIL_LEVEL_PARAM, TAIL_LEVEL_INPUT, TAIL_LEVEL_POT_PARAM, 0.f, 1.f) // tail level 195 | ); 196 | 197 | p_frequency = args.sampleRate; 198 | } 199 | } 200 | 201 | if (gverbR == NULL) { 202 | if (inputs[RIGHT_AUDIO].getChannels() > 0) { 203 | gverbR = gverb_new( 204 | args.sampleRate, // freq 205 | 300, // max room size 206 | getParam(ROOM_SIZE_PARAM, ROOM_SIZE_INPUT, ROOM_SIZE_POT_PARAM, 2.f, 300.f), // room size 207 | getParam(REV_TIME_PARAM, REV_TIME_INPUT, REV_TIME_POT_PARAM, 0.f, 10000.f), // revtime 208 | getParam(DAMPING_PARAM, DAMPING_INPUT, DAMPING_POT_PARAM, 0.f, 1.f), // damping 209 | 90.0, // spread 210 | getParam(BANDWIDTH_PARAM, BANDWIDTH_INPUT, BANDWIDTH_POT_PARAM, 0.f, 1.f), // input bandwidth 211 | getParam(EARLY_LEVEL_PARAM, EARLY_LEVEL_INPUT, EARLY_LEVEL_POT_PARAM, 0.f, 1.f), // early level 212 | getParam(TAIL_LEVEL_PARAM, TAIL_LEVEL_INPUT, TAIL_LEVEL_POT_PARAM, 0.f, 1.f) // tail level 213 | ); 214 | 215 | p_frequency = args.sampleRate; 216 | } 217 | } 218 | 219 | 220 | if (gverbL != NULL || gverbR != NULL) { 221 | handleParam(getParam(ROOM_SIZE_PARAM, ROOM_SIZE_INPUT, ROOM_SIZE_POT_PARAM, 2.f, 300.f), &p_room_size, gverb_set_roomsize); 222 | handleParam(getParam(REV_TIME_PARAM, REV_TIME_INPUT, REV_TIME_POT_PARAM, 0.f, 10.f), &p_rev_time, gverb_set_revtime); 223 | handleParam(getParam(DAMPING_PARAM, DAMPING_INPUT, DAMPING_POT_PARAM, 0.f, 1.f), &p_damping, gverb_set_damping); 224 | handleParam(getParam(BANDWIDTH_PARAM, BANDWIDTH_INPUT, BANDWIDTH_POT_PARAM, 0.f, 1.f), &p_bandwidth, gverb_set_inputbandwidth); 225 | handleParam(getParam(EARLY_LEVEL_PARAM, EARLY_LEVEL_INPUT, EARLY_LEVEL_POT_PARAM, 0.f, 1.f), &p_early_level, gverb_set_earlylevel); 226 | handleParam(getParam(TAIL_LEVEL_PARAM, TAIL_LEVEL_INPUT, TAIL_LEVEL_POT_PARAM, 0.f, 1.f), &p_tail_level, gverb_set_taillevel); 227 | 228 | auto engineCount = gverbL != NULL && gverbR != NULL ? 2 : 1; 229 | auto spread = getParam(SPREAD_PARAM, SPREAD_INPUT, SPREAD_POT_PARAM, 0.f, 1.f); 230 | auto L_L = 0.f, L_R = 0.f; 231 | auto R_L = 0.f, R_R = 0.f; 232 | 233 | if (gverbL != NULL) { 234 | gverb_do(gverbL, leftAudioIn / 10.f, &L_L, &L_R); 235 | 236 | L_L = isfinite(L_L) ? L_L * 10.f : 0.f; 237 | L_R = isfinite(L_R) ? L_R * 10.f : 0.f; 238 | } 239 | 240 | auto L_L_S = (L_L + ((1-spread) * L_R)) / (2-spread); 241 | auto L_R_S = (L_R + ((1-spread) * L_L)) / (2-spread); 242 | 243 | if (gverbR != NULL) { 244 | gverb_do(gverbR, rightAudioIn / 10.f, &R_L, &R_R); 245 | 246 | R_L = isfinite(R_L) ? R_L * 10.f : 0.f; 247 | R_R = isfinite(R_R) ? R_R * 10.f : 0.f; 248 | } 249 | 250 | auto R_L_S = (R_L + ((1-spread) * R_R)) / (2-spread); 251 | auto R_R_S = (R_R + ((1-spread) * R_L)) / (2-spread); 252 | 253 | auto outputLeft = L_L_S + R_L_S / engineCount; 254 | auto outputRight = L_R_S + R_R_S / engineCount; 255 | 256 | follower.step(&outputLeft, &outputRight); 257 | 258 | outputs[LEFT_OUTPUT].setVoltage( 259 | crossfade( 260 | inputs[LEFT_AUDIO].getChannels() > 0 ? leftAudioIn : rightAudioIn, 261 | outputLeft, 262 | mix) 263 | ); 264 | 265 | outputs[RIGHT_OUTPUT].setVoltage( 266 | crossfade( 267 | inputs[RIGHT_AUDIO].getChannels() > 0 ? rightAudioIn : leftAudioIn, 268 | outputRight, 269 | mix) 270 | ); 271 | 272 | } else { 273 | outputs[LEFT_OUTPUT].setVoltage(0.f); 274 | outputs[RIGHT_OUTPUT].setVoltage(0.f); 275 | } 276 | } 277 | 278 | struct GVerbModuleWidget : BaseWidget { 279 | GVerbModuleWidget(GVerbModule *module) { 280 | setModule(module); 281 | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Reverb.svg"))); 282 | 283 | addParam(createParam(Vec(50, 44), module, GVerbModule::ROOM_SIZE_PARAM)); 284 | addParam(createParam(Vec(50, 115), module, GVerbModule::DAMPING_PARAM)); 285 | 286 | addParam(createParam(Vec(127, 60), module, GVerbModule::REV_TIME_PARAM)); 287 | addParam(createParam(Vec(127, 120), module, GVerbModule::BANDWIDTH_PARAM)); 288 | addParam(createParam(Vec(185, 60), module, GVerbModule::EARLY_LEVEL_PARAM)); 289 | addParam(createParam(Vec(185, 120), module, GVerbModule::TAIL_LEVEL_PARAM)); 290 | 291 | addParam(createParam(Vec(84, 189), module, GVerbModule::MIX_PARAM)); 292 | addParam(createParam(Vec(135, 189), module, GVerbModule::SPREAD_PARAM)); 293 | addParam(createParam(Vec(186, 189), module, GVerbModule::RESET_PARAM)); 294 | 295 | addParam(createParam(Vec(15, 263), module, GVerbModule::ROOM_SIZE_POT_PARAM)); 296 | addParam(createParam(Vec(42, 263), module, GVerbModule::DAMPING_POT_PARAM)); 297 | addParam(createParam(Vec(70, 263), module, GVerbModule::REV_TIME_POT_PARAM)); 298 | addParam(createParam(Vec(97, 263), module, GVerbModule::BANDWIDTH_POT_PARAM)); 299 | addParam(createParam(Vec(124, 263), module, GVerbModule::EARLY_LEVEL_POT_PARAM)); 300 | addParam(createParam(Vec(151, 263), module, GVerbModule::TAIL_LEVEL_POT_PARAM)); 301 | addParam(createParam(Vec(178, 263), module, GVerbModule::MIX_POT_PARAM)); 302 | addParam(createParam(Vec(205, 263), module, GVerbModule::SPREAD_POT_PARAM)); 303 | 304 | addInput(createInput(Vec(14, 286), module, GVerbModule::ROOM_SIZE_INPUT)); 305 | addInput(createInput(Vec(41, 286), module, GVerbModule::DAMPING_INPUT)); 306 | addInput(createInput(Vec(68, 286), module, GVerbModule::REV_TIME_INPUT)); 307 | addInput(createInput(Vec(95, 286), module, GVerbModule::BANDWIDTH_INPUT)); 308 | addInput(createInput(Vec(123, 286), module, GVerbModule::EARLY_LEVEL_INPUT)); 309 | addInput(createInput(Vec(150, 286), module, GVerbModule::TAIL_LEVEL_INPUT)); 310 | addInput(createInput(Vec(177, 286), module, GVerbModule::MIX_INPUT)); 311 | addInput(createInput(Vec(204, 286), module, GVerbModule::SPREAD_INPUT)); 312 | addInput(createInput(Vec(232, 286), module, GVerbModule::RESET_INPUT)); 313 | 314 | addInput(createInput(Vec(14, 332), module, GVerbModule::LEFT_AUDIO)); 315 | addInput(createInput(Vec(41, 332), module, GVerbModule::RIGHT_AUDIO)); 316 | 317 | addOutput(createOutput(Vec(204, 332), module, GVerbModule::LEFT_OUTPUT)); 318 | addOutput(createOutput(Vec(232, 332), module, GVerbModule::RIGHT_OUTPUT)); 319 | 320 | initColourChange(Rect(Vec(111.572, 10), Vec(46.856, 13)), module, 0.06667f, 1.f, 0.58f); 321 | } 322 | }; 323 | 324 | 325 | // Specify the Module and ModuleWidget subclass, human-readable 326 | // author name for categorization per plugin, module slug (should never 327 | // change), human-readable module name, and any number of tags 328 | // (found in `include/tags.hpp`) separated by commas. 329 | Model *modelGVerbModule = createModel("rcm-gverb"); 330 | -------------------------------------------------------------------------------- /src/PianoRoll/PianoRollModule.cpp: -------------------------------------------------------------------------------- 1 | #include "PianoRollModule.hpp" 2 | 3 | using namespace rack; 4 | 5 | static const float PLUGGED_GATE_DURATION = std::numeric_limits::max(); 6 | static const float AUDITION_GATE_DURATION = std::numeric_limits::max(); 7 | static const float UNPLUGGED_GATE_DURATION = 2.0f; 8 | 9 | 10 | PianoRollModule::PianoRollModule() : BaseModule(), runInputActive(false), transport(&patternData) { 11 | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); 12 | processDivider.setDivision(32); 13 | } 14 | 15 | void PianoRollModule::onReset() { 16 | transport.reset(); 17 | patternData.reset(); 18 | } 19 | 20 | json_t *PianoRollModule::dataToJson() { 21 | json_t *rootJ = BaseModule::dataToJson(); 22 | if (rootJ == NULL) { 23 | rootJ = json_object(); 24 | } 25 | 26 | json_object_set_new(rootJ, "patterns", patternData.dataToJson()); 27 | json_object_set_new(rootJ, "currentPattern", json_integer(transport.currentPattern())); 28 | json_object_set_new(rootJ, "currentStep", json_integer(transport.currentStepInPattern())); 29 | json_object_set_new(rootJ, "clockDelay", json_integer(clockDelay)); 30 | json_object_set_new(rootJ, "sequenceRunning", json_boolean(transport.isRunning())); 31 | json_object_set_new(rootJ, "lowestDisplayNote", json_integer(this->state.lowestDisplayNote)); 32 | json_object_set_new(rootJ, "notesToShow", json_integer(this->state.notesToShow)); 33 | json_object_set_new(rootJ, "currentMeasure", json_integer(this->state.currentMeasure)); 34 | json_object_set_new(rootJ, "driverMode", json_boolean(this->driverMode)); 35 | 36 | return rootJ; 37 | } 38 | 39 | void PianoRollModule::dataFromJson(json_t *rootJ) { 40 | BaseModule::dataFromJson(rootJ); 41 | 42 | json_t *clockDelayJ = json_object_get(rootJ, "clockDelay"); 43 | if (clockDelayJ) { 44 | clockDelay = json_integer_value(clockDelayJ); 45 | } 46 | 47 | json_t *patternsJ = json_object_get(rootJ, "patterns"); 48 | if (patternsJ) { 49 | patternData.dataFromJson(patternsJ); 50 | } 51 | 52 | json_t *currentPatternJ = json_object_get(rootJ, "currentPattern"); 53 | if (currentPatternJ) { 54 | transport.setPattern(json_integer_value(currentPatternJ)); 55 | } 56 | 57 | json_t *currentStepJ = json_object_get(rootJ, "currentStep"); 58 | if (currentStepJ) { 59 | transport.setStepInPattern(json_integer_value(currentStepJ)); 60 | } 61 | 62 | json_t *sequenceRunningJ = json_object_get(rootJ, "sequenceRunning"); 63 | if (sequenceRunningJ) { 64 | transport.setRun(json_boolean_value(sequenceRunningJ)); 65 | } 66 | 67 | json_t *lowestDisplayNoteJ = json_object_get(rootJ, "lowestDisplayNote"); 68 | if (lowestDisplayNoteJ) { 69 | this->state.lowestDisplayNote = json_integer_value(lowestDisplayNoteJ); 70 | this->state.dirty = true; 71 | } 72 | 73 | json_t *notesToShowJ = json_object_get(rootJ, "notesToShow"); 74 | if (notesToShowJ) { 75 | this->state.notesToShow = json_integer_value(notesToShowJ); 76 | this->state.dirty = true; 77 | } 78 | 79 | json_t *currentMeasureJ = json_object_get(rootJ, "currentMeasure"); 80 | if (currentMeasureJ) { 81 | this->state.currentMeasure = json_integer_value(currentMeasureJ); 82 | this->state.dirty = true; 83 | } 84 | 85 | json_t *driverModeJ = json_object_get(rootJ, "driverMode"); 86 | if (driverModeJ) { 87 | this->driverMode = json_boolean_value(driverModeJ); 88 | } 89 | 90 | } 91 | 92 | int quantizePitch(float voct) { 93 | int oct = floor(voct); 94 | int note = abs(static_cast( roundf( ( voct * 12.0f) ) ) ) % 12; 95 | if (voct < 0.0f && note > 0) { 96 | note = 12 - note; 97 | } 98 | 99 | return ((oct + 4) * 12) + note; 100 | } 101 | 102 | void PianoRollModule::onAdd() { 103 | patternData.moduleId = this->id; 104 | } 105 | 106 | void PianoRollModule::process(const ProcessArgs &args) { 107 | if (!processDivider.process()) return; 108 | 109 | bool clockTick = false; 110 | 111 | while((int)clockBuffer.size() <= clockDelay) { 112 | clockBuffer.push(transport.isRunning() ? inputs[CLOCK_INPUT].getVoltage() : 0.f); 113 | } 114 | 115 | float currentClockLevel = 0.f; 116 | 117 | while((int)clockBuffer.size() > clockDelay) { 118 | currentClockLevel = clockBuffer.shift(); 119 | clockTick |= clockInputTrigger.process(currentClockLevel); 120 | } 121 | 122 | if (resetInputTrigger.process(inputs[RESET_INPUT].getVoltage())) { 123 | transport.reset(); 124 | gateOutputPulse.reset(); 125 | if (currentClockLevel > 1.f || driverMode) { 126 | clockTick = true; 127 | } 128 | } 129 | 130 | if (inputs[PATTERN_INPUT].getChannels() > 0) { 131 | int nextPattern = clamp(quantizePitch(inputs[PATTERN_INPUT].getVoltage()) - 48, 0, 63); 132 | transport.setPattern(nextPattern); 133 | } 134 | 135 | if (recordingIn.process(inputs[RECORD_INPUT].getVoltage())) { 136 | transport.toggleRecording(); 137 | } 138 | 139 | if (runInputTrigger.process(inputs[RUN_INPUT].getVoltage())) { 140 | transport.toggleRun(); 141 | 142 | if ( (currentClockLevel > 1.f && transport.currentStepInPattern() == -1) || (driverMode && transport.currentStepInPattern() == -1)) { 143 | clockTick = true; 144 | } 145 | 146 | if (!transport.isRunning()) { 147 | gateOutputPulse.reset(); 148 | } 149 | } 150 | 151 | if (clockTick) { 152 | transport.advanceStep(); 153 | } 154 | 155 | runInputActive.process(inputs[RUN_INPUT].getChannels() > 0); 156 | 157 | if (runInputActive.changed && transport.isRunning()) { 158 | if (runInputActive.value == true) { 159 | bool triggerGateAgain = gateOutputPulse.process(0); 160 | gateOutputPulse.reset(); 161 | if (triggerGateAgain) { 162 | // We've plugged in, the sequence is running and our gate is high 163 | // Trigger the gate for the full plugged in duration (forever) 164 | gateOutputPulse.trigger(PLUGGED_GATE_DURATION); 165 | } 166 | } 167 | 168 | if (runInputActive.value == false) { 169 | float gateTimeRemaining = gateOutputPulse.remaining; 170 | bool triggerGateAgain = gateOutputPulse.process(0) && gateTimeRemaining > 0; 171 | gateOutputPulse.reset(); 172 | if (triggerGateAgain) { 173 | // We've unplugged and the sequence is running and the gate is high 174 | // retrigger it for the time remaining if it had been triggered 175 | // when the cable was already unplugged. This is to prevent the gate sounding 176 | // forever - even when the clock is stopped 177 | gateOutputPulse.trigger(gateTimeRemaining); 178 | } 179 | } 180 | } 181 | 182 | if (!transport.isRecording()) { 183 | voctInBuffer.clear(); 184 | gateInBuffer.clear(); 185 | retriggerInBuffer.clear(); 186 | velocityInBuffer.clear(); 187 | } 188 | 189 | if (transport.isRecording() && transport.isRunning()) { 190 | 191 | while (!voctInBuffer.full()) { voctInBuffer.push(inputs[VOCT_INPUT].getVoltage()); } 192 | while (!gateInBuffer.full()) { gateInBuffer.push(inputs[GATE_INPUT].getVoltage()); } 193 | while (!retriggerInBuffer.full()) { retriggerInBuffer.push(inputs[RETRIGGER_INPUT].getVoltage()); } 194 | while (!velocityInBuffer.full()) { velocityInBuffer.push(inputs[VELOCITY_INPUT].getVoltage()); } 195 | 196 | int pattern = transport.currentPattern(); 197 | int measure = transport.currentMeasure(); 198 | int stepInMeasure = transport.currentStepInMeasure(); 199 | 200 | if (inputs[VOCT_INPUT].getChannels() > 0) { 201 | auto voctIn = voctInBuffer.shift(); 202 | patternData.setStepPitch(pattern, measure, stepInMeasure, quantizePitch(voctIn)); 203 | } 204 | 205 | if (inputs[GATE_INPUT].getChannels() > 0) { 206 | auto gateIn = gateInBuffer.shift(); 207 | 208 | if (clockTick && gateIn < 0.1f) { 209 | // Only turn off at the start of the step, user may let go early - we still want this step active 210 | patternData.setStepActive(pattern, measure, stepInMeasure, false); 211 | } 212 | 213 | if (gateIn >= 1.f) { 214 | patternData.setStepActive(pattern, measure, stepInMeasure, true); 215 | } 216 | } 217 | 218 | if (inputs[RETRIGGER_INPUT].getChannels() > 0) { 219 | auto retriggerIn = retriggerInBuffer.shift(); 220 | 221 | if (clockTick && retriggerIn < 0.1f) { 222 | // Only turn off at the start of the step, this will only trigger briefly within the step 223 | patternData.setStepRetrigger(pattern, measure, stepInMeasure, false); 224 | } 225 | 226 | if (retriggerIn >= 1.f) { 227 | patternData.setStepRetrigger(pattern, measure, stepInMeasure, true); 228 | } 229 | } 230 | 231 | if (inputs[VELOCITY_INPUT].getChannels() > 0) { 232 | auto velocityIn = velocityInBuffer.shift(); 233 | 234 | if (clockTick) { 235 | patternData.setStepVelocity(pattern, measure, stepInMeasure, 0.f); 236 | } 237 | 238 | if (velocityIn > 0.f) { 239 | patternData.increaseStepVelocityTo(pattern, measure, stepInMeasure, rescale(velocityIn, 0.f, 10.f, 0.f, 1.f)); 240 | } 241 | } 242 | 243 | } 244 | 245 | if (auditioner.isAuditioning()) { 246 | int pattern = transport.currentPattern(); 247 | int measure = auditioner.stepToAudition() / patternData.getStepsPerMeasure(pattern); 248 | int stepInMeasure = auditioner.stepToAudition() % patternData.getStepsPerMeasure(pattern); 249 | 250 | if (patternData.isStepActive(pattern, measure, stepInMeasure)) { 251 | bool retrigger = auditioner.consumeRetrigger(); 252 | 253 | if (retrigger) { 254 | retriggerOutputPulse.trigger(1e-3f); 255 | } 256 | 257 | gateOutputPulse.trigger(AUDITION_GATE_DURATION); 258 | 259 | outputs[VELOCITY_OUTPUT].setChannels(1); 260 | outputs[VELOCITY_OUTPUT].setVoltage(patternData.getStepVelocity(pattern, measure, stepInMeasure) * 10.f); 261 | 262 | float octave = patternData.getStepPitch(pattern, measure, stepInMeasure) / 12; 263 | float semitone = patternData.getStepPitch(pattern, measure, stepInMeasure) % 12; 264 | 265 | outputs[VELOCITY_OUTPUT].setChannels(1); 266 | outputs[VOCT_OUTPUT].setVoltage((octave-4.f) + ((1.f/12.f) * semitone)); 267 | } 268 | } 269 | 270 | if (auditioner.consumeStopEvent()) { 271 | gateOutputPulse.reset(); 272 | } 273 | 274 | if (((transport.isRunning() && clockTick)) && !transport.isRecording()) { 275 | if (transport.isLastStepOfPattern()) { 276 | eopOutputPulse.trigger(1e-3f); 277 | } 278 | 279 | int pattern = transport.currentPattern(); 280 | int measure = transport.currentMeasure(); 281 | int stepInMeasure = transport.currentStepInMeasure(); 282 | 283 | if (patternData.isStepActive(pattern, measure, stepInMeasure)) { 284 | if (gateOutputPulse.process(0) == false || patternData.isStepRetriggered(pattern, measure, stepInMeasure)) { 285 | retriggerOutputPulse.trigger(1e-3f); 286 | } 287 | 288 | gateOutputPulse.trigger(runInputActive.value ? PLUGGED_GATE_DURATION : UNPLUGGED_GATE_DURATION); 289 | 290 | outputs[VELOCITY_OUTPUT].setChannels(1); 291 | outputs[VELOCITY_OUTPUT].setVoltage(patternData.getStepVelocity(pattern, measure, stepInMeasure) * 10.f); 292 | 293 | float octave = patternData.getStepPitch(pattern, measure, stepInMeasure) / 12; 294 | float semitone = patternData.getStepPitch(pattern, measure, stepInMeasure) % 12; 295 | 296 | outputs[VOCT_OUTPUT].setChannels(1); 297 | outputs[VOCT_OUTPUT].setVoltage((octave-4.f) + ((1.f/12.f) * semitone)); 298 | 299 | } else { 300 | gateOutputPulse.reset(); 301 | } 302 | } 303 | 304 | outputs[RETRIGGER_OUTPUT].setChannels(1); 305 | outputs[RETRIGGER_OUTPUT].setVoltage(retriggerOutputPulse.process(args.sampleTime * processDivider.getDivision()) ? 10.f : 0.f); 306 | outputs[GATE_OUTPUT].setChannels(1); 307 | outputs[GATE_OUTPUT].setVoltage(gateOutputPulse.process(args.sampleTime * processDivider.getDivision()) ? 10.f : 0.f); 308 | if (outputs[RETRIGGER_OUTPUT].getChannels() == 0 && outputs[RETRIGGER_OUTPUT].getVoltage() > 0.f) { 309 | // If we're not using the retrigger output, the gate output to 0 for the trigger duration instead 310 | outputs[GATE_OUTPUT].setVoltage(0.f); 311 | } 312 | outputs[END_OF_PATTERN_OUTPUT].setChannels(1); 313 | outputs[END_OF_PATTERN_OUTPUT].setVoltage(eopOutputPulse.process(args.sampleTime * processDivider.getDivision()) ? 10.f : 0.f); 314 | 315 | if (inputs[GATE_INPUT].getChannels() > 0 && inputs[GATE_INPUT].getVoltage() > 1.f) { 316 | if (inputs[VOCT_INPUT].getChannels() > 0) { outputs[VOCT_OUTPUT].setVoltage(inputs[VOCT_INPUT].getVoltage()); } 317 | if (inputs[GATE_INPUT].getChannels() > 0) { 318 | if (outputs[RETRIGGER_OUTPUT].getChannels() == 0 && inputs[RETRIGGER_INPUT].getChannels() > 0) { 319 | outputs[GATE_OUTPUT].setVoltage(inputs[GATE_INPUT].getVoltage() - inputs[RETRIGGER_INPUT].getVoltage()); 320 | } else { 321 | outputs[GATE_OUTPUT].setVoltage(inputs[GATE_INPUT].getVoltage()); 322 | } 323 | } 324 | if (inputs[RETRIGGER_INPUT].getChannels() > 0) { outputs[RETRIGGER_OUTPUT].setVoltage(inputs[RETRIGGER_INPUT].getVoltage()); } 325 | if (inputs[VELOCITY_INPUT].getChannels() > 0) { outputs[VELOCITY_OUTPUT].setVoltage(inputs[VELOCITY_INPUT].getVoltage()); } 326 | } 327 | 328 | // Send our chaining outputs 329 | outputs[CLOCK_OUTPUT].setChannels(1); 330 | outputs[RESET_OUTPUT].setChannels(1); 331 | outputs[PATTERN_OUTPUT].setChannels(1); 332 | outputs[RUN_OUTPUT].setChannels(1); 333 | outputs[RECORD_OUTPUT].setChannels(1); 334 | 335 | outputs[CLOCK_OUTPUT].setVoltage(inputs[CLOCK_INPUT].getVoltage()); 336 | outputs[RESET_OUTPUT].setVoltage(inputs[RESET_INPUT].getVoltage()); 337 | outputs[PATTERN_OUTPUT].setVoltage(transport.currentPattern() * (1.f/12.f)); 338 | outputs[RUN_OUTPUT].setVoltage(inputs[RUN_INPUT].getVoltage()); 339 | outputs[RECORD_OUTPUT].setVoltage(inputs[RECORD_INPUT].getVoltage()); 340 | } 341 | --------------------------------------------------------------------------------