commit 7e460b770b33a66076a1cb69b06ec2680f280ea3
parent 8e09f6a0f692814877ca70114790f432ad886369
Author: Matt Demanett <matt@demanett.net>
Date: Thu, 16 Nov 2017 00:55:07 -0500
Initial commit. Shaper(+), DADSRH(+), Offset and S&H modules.
Diffstat:
37 files changed, 2062 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
@@ -0,0 +1,11 @@
+
+SOURCES = $(wildcard src/*.cpp)
+
+include ../../plugin.mk
+
+
+dist: all
+ mkdir -p dist/Fundamental
+ cp LICENSE* dist/Fundamental/
+ cp plugin.* dist/Fundamental/
+ cp -R res dist/Fundamental/
diff --git a/README.md b/README.md
@@ -1,2 +1,62 @@
# BogaudioModules
-Modules for VCV Rack.
+Modules for [VCV Rack](https://github.com/VCVRack/Rack), an open-source Eurorack modular synthesizer.
+
+
+
+## Building
+
+You'll need to be set up to build [VCV Rack](https://github.com/VCVRack/Rack) itself. Switch to the `plugins/` directory there, then:
+
+ ```
+ git clone https://github.com/bogaudio/BogaudioModules.git
+ cd BogaudioModules
+ make
+ ```
+
+## Modules
+
+### Envelopes
+
+#### SHAPER
+
+SHAPER emulates the function of the Envelope Generator section of the classic [EMS VC3](https://en.wikipedia.org/wiki/EMS_VCS_3) and related synths. It combines an envelope with a VCA. Unlike an ADSR, the envelope stages are Attack, On, Decay and Off -- with linear movement in the attack and decay stages, this produces a signature trapezoidal envelope shape.
+
+Features:
+ - The ATTACK, ON, DECAY and OFF knobs specify times from nearly zero to 10 seconds. The Speed switch allows these times to be multiplied by 10.
+ - The trapezoid envelope is output as a 0-10 control signal at port ENV, subject to attenuation by the ENV knob. (INV outputs the inverse envelope.)
+ - Audio input at port IN is sent through the internal VCA -- controlled by knob SIGNAL and the envelope -- to port OUT.
+ - A trigger CV at the TRIGGER port, or a press of the TRIGGER button, will start the envelope cycle. When the off stage completes, a trigger is sent out at port END. If the CYCLE switch is set to LOOP, the envelope restarts immediately.
+
+#### SHAPER+
+
+SHAPER+ is a SHAPER, with the addition of CV inputs for each knob, and gate outputs for each stage (a stage's gate output will be high for the duration of the stage).
+
+#### DADSRH
+
+DADSRH (Delay Attack Decay Sustain Release Hold) augments a standard ADSR with a delay stage and a self-gating (hold) mode.
+
+Features:
+ - When the MODE switch is set to GATE, DADSRH is a more-or-less standard ADSR envelope generator, with an additional pre-attack delay stage. The envelope is controlled by a gate CV at the trigger port, or by holding the TRIGGER button.
+ - When MODE is TRIG, a trigger CV or press of the TRIGGER button will start a normal DADSR cycle, but controlled by an internal gate CV. The internal gate persists for the time set by the HOLD knob.
+ - The envelope is output as a 0-10 signal at port ENV. Its inverse (actually, 10 - ENV) is output at INV. When a release stage completes, a trigger is output at END.
+ - When MODE is TRIGGER, the CYCLE switch controls whether the envelope loops or not upon completion of a release stage.
+ - Toggles allow selection of linear, exponential or inverse-exponential shapes for the attack, decay and release stages.
+ - The RETRIG switch controls the retrigger behavior (when a new gate or trigger happens while the envelope is running): ATT immediately attacks from the current envelope value (this is the typical behavior with many ADSRs), while RST causes a full reset of the envelope (restarting it at the delay stage).
+
+#### DADSRH+
+
+DADSRH+ is a DADSRH, with the addition of CV inputs for each knob, and gate outputs for each stage (a stage's gate output will be high for the duration of the stage).
+
+### Utils
+
+#### OFFSET
+
+A 3-HP CV offset and attenuverter. The OFFSET and ATTEN knobs have CV inputs.
+
+#### S&H
+
+A 3-HP, dual sample-and-hold. Sampling may be triggered by CV or button press. If nothing is connected to an IN port, sampling for that channel is from an internal white noise source (range 0-10).
+
+## Issues and Feedback
+
+Bug reports and feedback are welcome: please use the [issue tracker](https://github.com/bogaudio/BogaudioModules/issues).
diff --git a/res/DADSRH-src.svg b/res/DADSRH-src.svg
Binary files differ.
diff --git a/res/DADSRH.svg b/res/DADSRH.svg
Binary files differ.
diff --git a/res/DADSRHPlus-src.svg b/res/DADSRHPlus-src.svg
Binary files differ.
diff --git a/res/DADSRHPlus.svg b/res/DADSRHPlus.svg
Binary files differ.
diff --git a/res/Offset-src.svg b/res/Offset-src.svg
Binary files differ.
diff --git a/res/Offset.svg b/res/Offset.svg
Binary files differ.
diff --git a/res/SampleHold-src.svg b/res/SampleHold-src.svg
Binary files differ.
diff --git a/res/SampleHold.svg b/res/SampleHold.svg
Binary files differ.
diff --git a/res/Shaper-src.svg b/res/Shaper-src.svg
Binary files differ.
diff --git a/res/Shaper.svg b/res/Shaper.svg
Binary files differ.
diff --git a/res/ShaperPlus-src.svg b/res/ShaperPlus-src.svg
Binary files differ.
diff --git a/res/ShaperPlus.svg b/res/ShaperPlus.svg
Binary files differ.
diff --git a/res/button_18px-src.svg b/res/button_18px-src.svg
Binary files differ.
diff --git a/res/button_18px.svg b/res/button_18px.svg
Binary files differ.
diff --git a/res/button_9px-src.svg b/res/button_9px-src.svg
Binary files differ.
diff --git a/res/button_9px.svg b/res/button_9px.svg
Binary files differ.
diff --git a/res/knob_29px-src.svg b/res/knob_29px-src.svg
Binary files differ.
diff --git a/res/knob_29px.svg b/res/knob_29px.svg
Binary files differ.
diff --git a/res/knob_38px-src.svg b/res/knob_38px-src.svg
Binary files differ.
diff --git a/res/knob_38px.svg b/res/knob_38px.svg
Binary files differ.
diff --git a/res/www/modules.png b/res/www/modules.png
Binary files differ.
diff --git a/scripts/svg_render.sh b/scripts/svg_render.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+INKSCAPE="/Applications/Inkscape.app/Contents/Resources/bin/inkscape"
+
+if [ "x$1" = "x" -o "x$2" = "x" ]
+then
+ echo "Usage: $0 <in.svg> <out.svg>"
+ exit 1
+fi
+
+if [ ! -f "$1" ]
+then
+ echo "No such file: $1"
+ exit 1
+fi
+
+TMP_FILE="/tmp/svg_render_tmp.svg"
+
+< "$1" perl -e '$s = do { local $/; <STDIN> }; $s =~ s/<use[^<>\/]+id="\w+_(PARAM|INPUT|OUTPUT|LIGHT)"[^<>\/]+(\/\>|<\/use>)//gs; print $s' > "$TMP_FILE" && \
+ "$INKSCAPE" -f "$TMP_FILE" \
+ --verb EditSelectAll --verb SelectionUnGroup \
+ --verb EditSelectAll --verb EditUnlinkClone \
+ --verb EditSelectAll --verb ObjectToPath \
+ --verb FileSave --verb FileQuit && \
+ cp "$TMP_FILE" "$2" && \
+ rm "$TMP_FILE"
diff --git a/scripts/svg_widgets.rb b/scripts/svg_widgets.rb
@@ -0,0 +1,182 @@
+#!/usr/bin/ruby
+
+INKSCAPE = '/Applications/Inkscape.app/Contents/Resources/bin/inkscape'
+OUTPUT_DECIMAL_PLACES=2
+
+require 'optparse'
+
+options = {
+ output: 'list',
+ variable_style: 'positions',
+ module: 'MODULE',
+ param_class: 'RoundBlackKnob',
+ input_class: 'PJ301MPort',
+ output_class: 'PJ301MPort',
+ light_class: 'TinyLight<GreenLight>',
+ comments: false,
+ sort: nil
+}
+option_parser = OptionParser.new do |opts|
+ opts.banner = "Usage: #{$0} [options] <svg file>"
+ opts.on('--list', 'Output list of widget IDs, positions and dimensions (default)') do
+ options[:output] = 'list'
+ end
+ opts.on('--ids', 'Output list of widget IDs only') do
+ options[:output] = 'ids'
+ end
+ opts.on('--variables=[STYLE]', %w(positions accessors parameters members initializers), 'Output variable declarations for each widget\'s position') do |v|
+ options[:output] = 'variables'
+ options[:variable_style] = v if v
+ end
+ opts.on('--creates', 'Output createParam, etc, lines for each widget') do
+ options[:output] = 'creates'
+ end
+ opts.on('--enums', 'Output param/input/output/light ID enums') do
+ options[:output] = 'enums'
+ end
+ opts.on('--module=MODULE', 'Name of the module class: used with --creates (default: MODULE)') do |v|
+ options[:module] = v
+ end
+ opts.on('--param-class=CLASS', 'Widget type for params: used with --creates (default: RoundBlackKnob)') do |v|
+ options[:param_class] = v
+ end
+ opts.on('--input-class=CLASS', 'Widget type for inputs: used with --creates (default: PJ301MPort)') do |v|
+ options[:input_class] = v
+ end
+ opts.on('--output-class=CLASS', 'Widget type for outputs: used with --creates (default: PJ301MPort)') do |v|
+ options[:output_class] = v
+ end
+ opts.on('--light-class=CLASS', 'Widget type for lights: used with --creates (default: "TinyLight<GreenLight>")') do |v|
+ options[:light_class] = v
+ end
+ opts.on('--comments', 'Output "generated by" comments around code') do
+ options[:comments] = true
+ end
+ opts.on('--sort=SORT', %w(ids position), 'Sort widgets for output; "ids" to sort alphabetically, "position" to sort top-down and left-right') do |v|
+ options[:sort] = v
+ end
+ opts.on_tail('-h', '--help', 'Show this message') do
+ puts opts
+ exit
+ end
+end
+begin
+ option_parser.parse!
+rescue => e
+ STDERR.puts e.to_s
+ STDERR.puts "\n"
+ STDERR.puts option_parser.help
+ exit 1
+end
+
+unless ARGV.size >= 1
+ STDERR.puts option_parser.help
+ exit 1
+end
+svg_file = ARGV[0]
+unless File.exist?(svg_file)
+ STDERR.puts "No such file: #{svg_file}"
+ exit 1
+end
+svg_file = File.absolute_path(svg_file)
+
+lines = `#{INKSCAPE} -z -S #{svg_file}`
+exit unless lines =~ /_(PARAM|INPUT|OUTPUT|LIGHT)/
+
+Widget = Struct.new(:id, :x, :y, :width, :height) do
+ def to_s
+ "#{id} x=#{x} y=#{y} width=#{width} x=#{height}"
+ end
+end
+widgets_by_type = {}
+widget_re = %r{^(\w+_(PARAM|INPUT|OUTPUT|LIGHT)),(\d+(?:\.\d+)?),(\d+(?:\.\d+)?),(\d+(?:\.\d+)?),(\d+(?:\.\d+)?)}
+lines.split.each do |line|
+ if m = widget_re.match(line)
+ widget = Widget.new(
+ m[1],
+ m[3].to_f.round(OUTPUT_DECIMAL_PLACES),
+ m[4].to_f.round(OUTPUT_DECIMAL_PLACES),
+ m[5].to_f.round(OUTPUT_DECIMAL_PLACES),
+ m[6].to_f.round(OUTPUT_DECIMAL_PLACES)
+ )
+ (widgets_by_type["#{m[2].downcase}s"] ||= []) << widget
+ end
+end
+
+if options[:sort]
+ %w(params inputs outputs lights).each do |type|
+ next unless widgets_by_type.key?(type)
+ widgets_by_type[type].sort! do |a, b|
+ case options[:sort]
+ when 'position'
+ a.y <=> b.y || a.x <=> b.x || a.id <=> b.id
+ else
+ a.id <=> b.id
+ end
+ end
+ end
+end
+
+def titleize(s)
+ return s unless s =~ /_/
+ ss = s.downcase.split(/_+/)
+ "#{ss[0]}#{ss[1..-1].map { |s| "#{s[0].upcase}#{s[1..-1]}" }.join('')}"
+end
+
+puts "// generated by #{File.basename($0)}" if options[:comments] && %(variables creates enums).include?(options[:output])
+case options[:output]
+when 'ids'
+ groups = %w(params inputs outputs lights).map do |type|
+ (widgets_by_type[type] || []).map(&:id)
+ end
+ puts groups.reject(&:empty?).map { |g| g.join("\n") }.join("\n\n")
+when 'variables'
+ groups = [%w(params Param), %w(inputs Input), %w(outputs Output), %w(lights Light)].map do |type|
+ (widgets_by_type[type[0]] || []).map do |w|
+ case options[:variable_style]
+ when 'accessors'
+ "#{type[0]}[#{w.id}];"
+ when 'parameters'
+ "#{type[1]}& #{titleize(w.id)},"
+ when 'members'
+ "#{type[1]}& _#{titleize(w.id)};"
+ when 'initializers'
+ s = titleize(w.id)
+ ", _#{s}(#{s})"
+ else
+ "auto #{titleize(w.id)}Position = Vec(#{w.x}, #{w.y});"
+ end
+ end
+ end
+ puts groups.reject(&:empty?).map { |g| g.join("\n") }.join("\n\n")
+when 'creates'
+ groups = []
+ groups << (widgets_by_type['params'] || []).map do |w|
+ "addParam(createParam<#{options[:param_class]}>(#{titleize(w.id)}Position, module, #{options[:module]}::#{w.id}, 0.0, 1.0, 0.5));"
+ end.join("\n")
+ groups << (widgets_by_type['inputs'] || []).map do |w|
+ "addInput(createInput<#{options[:input_class]}>(#{titleize(w.id)}Position, module, #{options[:module]}::#{w.id}));"
+ end.join("\n")
+ groups << (widgets_by_type['outputs'] || []).map do |w|
+ "addOutput(createOutput<#{options[:output_class]}>(#{titleize(w.id)}Position, module, #{options[:module]}::#{w.id}));"
+ end.join("\n")
+ groups << (widgets_by_type['lights'] || []).map do |w|
+ "addChild(createLight<#{options[:light_class]}>(#{titleize(w.id)}Position, module, #{options[:module]}::#{w.id}));"
+ end.join("\n")
+ puts groups.reject(&:empty?).join("\n\n")
+when 'enums'
+ groups = %w(Params Inputs Outputs Lights).map do |type|
+ "enum #{type}Ids {\n #{(widgets_by_type[type.downcase] || []).map(&:id).join(",\n ")},\n NUM_#{type.upcase}\n};"
+ end
+ puts groups.join("\n\n")
+else
+ puts "Params:"
+ puts widgets_by_type['params']
+ puts "\nInputs:"
+ puts widgets_by_type['inputs']
+ puts "\nOutputs:"
+ puts widgets_by_type['outputs']
+ puts "\nLights:"
+ puts widgets_by_type['lights']
+end
+puts "// end generated by #{File.basename($0)}" if options[:comments] && options[:output] != 'list'
diff --git a/src/BogaudioModules.cpp b/src/BogaudioModules.cpp
@@ -0,0 +1,17 @@
+#include "BogaudioModules.hpp"
+
+Plugin *plugin;
+
+void init(rack::Plugin *p) {
+ plugin = p;
+ p->slug = "Bogaudio";
+#ifdef VERSION
+ p->version = TOSTRING(VERSION);
+#endif
+ p->addModel(createModel<ShaperWidget>("Bogaudio", "Bogaudio-Shaper", "Shaper", ENVELOPE_GENERATOR_TAG, AMPLIFIER_TAG));
+ p->addModel(createModel<ShaperPlusWidget>("Bogaudio", "Bogaudio-ShaperPlus", "Shaper+", ENVELOPE_GENERATOR_TAG, AMPLIFIER_TAG));
+ p->addModel(createModel<DADSRHWidget>("Bogaudio", "Bogaudio-DADSRH", "DADSR(H)", ENVELOPE_GENERATOR_TAG));
+ p->addModel(createModel<DADSRHPlusWidget>("Bogaudio", "Bogaudio-DADSRHPlus", "DADSR(H)+", ENVELOPE_GENERATOR_TAG));
+ p->addModel(createModel<OffsetWidget>("Bogaudio", "Bogaudio-Offset", "Offset/Attenuverter", ATTENUATOR_TAG));
+ p->addModel(createModel<SampleHoldWidget>("Bogaudio", "Bogaudio-SampleHold", "Sample & Hold", SAMPLE_AND_HOLD_TAG, DUAL_TAG));
+}
diff --git a/src/BogaudioModules.hpp b/src/BogaudioModules.hpp
@@ -0,0 +1,64 @@
+#include "rack.hpp"
+
+using namespace rack;
+
+extern Plugin *plugin;
+
+struct ShaperWidget : ModuleWidget {
+ ShaperWidget();
+};
+
+struct ShaperPlusWidget : ModuleWidget {
+ ShaperPlusWidget();
+};
+
+struct DADSRHWidget : ModuleWidget {
+ DADSRHWidget();
+};
+
+struct DADSRHPlusWidget : ModuleWidget {
+ DADSRHPlusWidget();
+};
+
+struct OffsetWidget : ModuleWidget {
+ OffsetWidget();
+};
+
+struct SampleHoldWidget : ModuleWidget {
+ SampleHoldWidget();
+};
+
+struct GaussWidget : ModuleWidget {
+ GaussWidget();
+};
+
+
+struct Knob29 : RoundKnob {
+ Knob29() {
+ setSVG(SVG::load(assetPlugin(plugin, "res/knob_29px.svg")));
+ box.size = Vec(29, 29);
+ }
+};
+
+struct Knob38 : RoundKnob {
+ Knob38() {
+ setSVG(SVG::load(assetPlugin(plugin, "res/knob_38px.svg")));
+ box.size = Vec(38, 38);
+ }
+};
+
+struct Button18 : SVGSwitch, MomentarySwitch {
+ Button18() {
+ addFrame(SVG::load(assetPlugin(plugin, "res/button_18px.svg")));
+ box.size = Vec(18, 18);
+ }
+};
+
+struct Button9Toggle3 : SVGSwitch, ToggleSwitch {
+ Button9Toggle3() {
+ addFrame(SVG::load(assetPlugin(plugin, "res/button_9px.svg")));
+ addFrame(SVG::load(assetPlugin(plugin, "res/button_9px.svg")));
+ addFrame(SVG::load(assetPlugin(plugin, "res/button_9px.svg")));
+ box.size = Vec(9, 9);
+ }
+};
diff --git a/src/DADSRH.cpp b/src/DADSRH.cpp
@@ -0,0 +1,212 @@
+
+#include "DADSRHCore.hpp"
+
+struct DADSRH : Module {
+ enum ParamsIds {
+ DELAY_PARAM,
+ ATTACK_PARAM,
+ DECAY_PARAM,
+ SUSTAIN_PARAM,
+ RELEASE_PARAM,
+ HOLD_PARAM,
+ ATTACK_SHAPE_PARAM,
+ DECAY_SHAPE_PARAM,
+ RELEASE_SHAPE_PARAM,
+ TRIGGER_PARAM,
+ MODE_PARAM,
+ LOOP_PARAM,
+ SPEED_PARAM,
+ RETRIGGER_PARAM,
+ NUM_PARAMS
+ };
+
+ enum InputsIds {
+ TRIGGER_INPUT,
+ NUM_INPUTS
+ };
+
+ enum OutputsIds {
+ ENV_OUTPUT,
+ INV_OUTPUT,
+ TRIGGER_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ enum LightsIds {
+ DELAY_LIGHT,
+ ATTACK_LIGHT,
+ DECAY_LIGHT,
+ SUSTAIN_LIGHT,
+ RELEASE_LIGHT,
+ ATTACK_SHAPE1_LIGHT,
+ ATTACK_SHAPE2_LIGHT,
+ ATTACK_SHAPE3_LIGHT,
+ DECAY_SHAPE1_LIGHT,
+ DECAY_SHAPE2_LIGHT,
+ DECAY_SHAPE3_LIGHT,
+ RELEASE_SHAPE1_LIGHT,
+ RELEASE_SHAPE2_LIGHT,
+ RELEASE_SHAPE3_LIGHT,
+ NUM_LIGHTS
+ };
+
+ DADSRHCore _core;
+
+ DADSRH() : Module(
+ NUM_PARAMS,
+ NUM_INPUTS,
+ NUM_OUTPUTS,
+ NUM_LIGHTS
+ )
+ , _core(
+ params[DELAY_PARAM],
+ params[ATTACK_PARAM],
+ params[DECAY_PARAM],
+ params[SUSTAIN_PARAM],
+ params[RELEASE_PARAM],
+ params[HOLD_PARAM],
+ params[ATTACK_SHAPE_PARAM],
+ params[DECAY_SHAPE_PARAM],
+ params[RELEASE_SHAPE_PARAM],
+ params[TRIGGER_PARAM],
+ params[MODE_PARAM],
+ params[LOOP_PARAM],
+ params[SPEED_PARAM],
+ params[RETRIGGER_PARAM],
+
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ inputs[TRIGGER_INPUT],
+
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ outputs[ENV_OUTPUT],
+ outputs[INV_OUTPUT],
+ outputs[TRIGGER_OUTPUT],
+
+ lights[DELAY_LIGHT],
+ lights[ATTACK_LIGHT],
+ lights[DECAY_LIGHT],
+ lights[SUSTAIN_LIGHT],
+ lights[RELEASE_LIGHT],
+ lights[ATTACK_SHAPE1_LIGHT],
+ lights[ATTACK_SHAPE2_LIGHT],
+ lights[ATTACK_SHAPE3_LIGHT],
+ lights[DECAY_SHAPE1_LIGHT],
+ lights[DECAY_SHAPE2_LIGHT],
+ lights[DECAY_SHAPE3_LIGHT],
+ lights[RELEASE_SHAPE1_LIGHT],
+ lights[RELEASE_SHAPE2_LIGHT],
+ lights[RELEASE_SHAPE3_LIGHT]
+ ) {
+ reset();
+ }
+
+ virtual void reset() override {
+ _core.reset();
+ }
+
+ virtual void step() override {
+ _core.step();
+ }
+};
+
+
+DADSRHWidget::DADSRHWidget() {
+ DADSRH *module = new DADSRH();
+ setModule(module);
+ box.size = Vec(RACK_GRID_WIDTH * 10, RACK_GRID_HEIGHT);
+
+ {
+ SVGPanel *panel = new SVGPanel();
+ panel->box.size = box.size;
+ panel->setBackground(SVG::load(assetPlugin(plugin, "res/DADSRH.svg")));
+ addChild(panel);
+ }
+
+ addChild(createScrew<ScrewSilver>(Vec(0, 0)));
+ addChild(createScrew<ScrewSilver>(Vec(box.size.x - 15, 0)));
+ addChild(createScrew<ScrewSilver>(Vec(0, 365)));
+ addChild(createScrew<ScrewSilver>(Vec(box.size.x - 15, 365)));
+
+ // generated by svg_widgets.rb
+ auto delayParamPosition = Vec(27.08, 33.08);
+ auto attackParamPosition = Vec(27.08, 89.08);
+ auto decayParamPosition = Vec(27.08, 145.08);
+ auto sustainParamPosition = Vec(27.08, 201.08);
+ auto releaseParamPosition = Vec(27.08, 257.08);
+ auto holdParamPosition = Vec(82.38, 313.08);
+ auto attackShapeParamPosition = Vec(77.02, 124.02);
+ auto decayShapeParamPosition = Vec(77.02, 180.52);
+ auto releaseShapeParamPosition = Vec(77.02, 292.52);
+ auto triggerParamPosition = Vec(90.04, 43.04);
+ auto modeParamPosition = Vec(119.9, 96.9);
+ auto loopParamPosition = Vec(119.9, 146.9);
+ auto speedParamPosition = Vec(19.9, 322.9);
+ auto retriggerParamPosition = Vec(54.9, 322.9);
+
+ auto triggerInputPosition = Vec(115.0, 40.0);
+
+ auto envOutputPosition = Vec(115.0, 191.0);
+ auto invOutputPosition = Vec(115.0, 228.0);
+ auto triggerOutputPosition = Vec(115.0, 265.0);
+
+ auto delayLightPosition = Vec(12.0, 76.0);
+ auto attackLightPosition = Vec(12.0, 123.0);
+ auto decayLightPosition = Vec(12.0, 179.0);
+ auto sustainLightPosition = Vec(12.0, 235.0);
+ auto releaseLightPosition = Vec(12.0, 291.0);
+ auto attackShape1LightPosition = Vec(77.0, 96.0);
+ auto attackShape2LightPosition = Vec(77.0, 106.0);
+ auto attackShape3LightPosition = Vec(77.0, 116.0);
+ auto decayShape1LightPosition = Vec(77.0, 152.5);
+ auto decayShape2LightPosition = Vec(77.0, 162.5);
+ auto decayShape3LightPosition = Vec(77.0, 172.5);
+ auto releaseShape1LightPosition = Vec(77.0, 264.5);
+ auto releaseShape2LightPosition = Vec(77.0, 274.5);
+ auto releaseShape3LightPosition = Vec(77.0, 284.5);
+ // end generated by svg_widgets.rb
+
+ addParam(createParam<Knob38>(delayParamPosition, module, DADSRH::DELAY_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParam<Knob38>(attackParamPosition, module, DADSRH::ATTACK_PARAM, 0.0, 1.0, 0.12));
+ addParam(createParam<Knob38>(decayParamPosition, module, DADSRH::DECAY_PARAM, 0.0, 1.0, 0.32));
+ addParam(createParam<Knob38>(sustainParamPosition, module, DADSRH::SUSTAIN_PARAM, 0.0, 1.0, 0.5));
+ addParam(createParam<Knob38>(releaseParamPosition, module, DADSRH::RELEASE_PARAM, 0.0, 1.0, 0.32));
+ addParam(createParam<Knob38>(holdParamPosition, module, DADSRH::HOLD_PARAM, 0.0, 1.0, 0.45));
+ addParam(createParam<Button9Toggle3>(attackShapeParamPosition, module, DADSRH::ATTACK_SHAPE_PARAM, 1.0, 3.0, 1.0));
+ addParam(createParam<Button9Toggle3>(decayShapeParamPosition, module, DADSRH::DECAY_SHAPE_PARAM, 1.0, 3.0, 1.0));
+ addParam(createParam<Button9Toggle3>(releaseShapeParamPosition, module, DADSRH::RELEASE_SHAPE_PARAM, 1.0, 3.0, 1.0));
+ addParam(createParam<Button18>(triggerParamPosition, module, DADSRH::TRIGGER_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParam<CKSS>(modeParamPosition, module, DADSRH::MODE_PARAM, 0.0, 1.0, 1.0));
+ addParam(createParam<CKSS>(loopParamPosition, module, DADSRH::LOOP_PARAM, 0.0, 1.0, 1.0));
+ addParam(createParam<CKSS>(speedParamPosition, module, DADSRH::SPEED_PARAM, 0.0, 1.0, 1.0));
+ addParam(createParam<CKSS>(retriggerParamPosition, module, DADSRH::RETRIGGER_PARAM, 0.0, 1.0, 1.0));
+
+ addInput(createInput<PJ301MPort>(triggerInputPosition, module, DADSRH::TRIGGER_INPUT));
+
+ addOutput(createOutput<PJ301MPort>(envOutputPosition, module, DADSRH::ENV_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(invOutputPosition, module, DADSRH::INV_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(triggerOutputPosition, module, DADSRH::TRIGGER_OUTPUT));
+
+ addChild(createLight<TinyLight<GreenLight>>(delayLightPosition, module, DADSRH::DELAY_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(attackLightPosition, module, DADSRH::ATTACK_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(decayLightPosition, module, DADSRH::DECAY_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(sustainLightPosition, module, DADSRH::SUSTAIN_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(releaseLightPosition, module, DADSRH::RELEASE_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(attackShape1LightPosition, module, DADSRH::ATTACK_SHAPE1_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(attackShape2LightPosition, module, DADSRH::ATTACK_SHAPE2_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(attackShape3LightPosition, module, DADSRH::ATTACK_SHAPE3_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(decayShape1LightPosition, module, DADSRH::DECAY_SHAPE1_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(decayShape2LightPosition, module, DADSRH::DECAY_SHAPE2_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(decayShape3LightPosition, module, DADSRH::DECAY_SHAPE3_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(releaseShape1LightPosition, module, DADSRH::RELEASE_SHAPE1_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(releaseShape2LightPosition, module, DADSRH::RELEASE_SHAPE2_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(releaseShape3LightPosition, module, DADSRH::RELEASE_SHAPE3_LIGHT));
+}
diff --git a/src/DADSRHCore.cpp b/src/DADSRHCore.cpp
@@ -0,0 +1,255 @@
+
+#include <math.h>
+#include "DADSRHCore.hpp"
+
+void DADSRHCore::reset() {
+ _trigger.reset();
+ _stage = STOPPED_STAGE;
+ _releaseLevel = _holdProgress = _stageProgress = _envelope = 0.0;
+}
+
+void DADSRHCore::step() {
+ const int SHAPE1 = 1;
+ const int SHAPE2 = 2;
+ const int SHAPE3 = 3;
+ const float shapeExponent = 2.0;
+ const float shapeInverseExponent = 0.5;
+
+ bool slow = _speedParam.value <= 0.5;
+ if (_trigger.process(_triggerParam.value + _triggerInput.value)) {
+ if (_stage == STOPPED_STAGE || _retriggerParam.value <= 0.5) {
+ _stage = DELAY_STAGE;
+ _holdProgress = _stageProgress = _envelope = 0.0;
+ }
+ else {
+ switch (_stage) {
+ case STOPPED_STAGE:
+ case ATTACK_STAGE: {
+ break;
+ }
+
+ case DELAY_STAGE: {
+ _stage = ATTACK_STAGE;
+ _stageProgress = _envelope = 0.0;
+
+ // we're skipping the delay; subtract the full delay time from hold time so that env has the same shape as if we'd waited out the delay.
+ float delayTime = knobTime(_delayParam, _delayInput, slow, true);
+ float holdTime = knobTime(_holdParam, _holdInput, slow);
+ _holdProgress = fminf(1.0, delayTime / holdTime);
+ break;
+ }
+
+ case DECAY_STAGE:
+ case SUSTAIN_STAGE:
+ case RELEASE_STAGE: {
+ _stage = ATTACK_STAGE;
+ switch ((int)_attackShapeParam.value) {
+ case SHAPE2: {
+ _stageProgress = _envelope;
+ break;
+ }
+ case SHAPE3: {
+ _stageProgress = pow(_envelope, shapeInverseExponent);
+ break;
+ }
+ default: {
+ _stageProgress = pow(_envelope, shapeExponent);
+ break;
+ }
+ }
+
+ // reset hold to what it would have been at this point in the attack the first time through.
+ float delayTime = knobTime(_delayParam, _delayInput, slow, true);
+ float attackTime = knobTime(_attackParam, _attackInput, slow);
+ float holdTime = knobTime(_holdParam, _holdInput, slow);
+ _holdProgress = fminf(1.0, (delayTime + _stageProgress * attackTime) / holdTime);
+ break;
+ }
+ }
+ }
+ }
+ else {
+ switch (_stage) {
+ case STOPPED_STAGE:
+ case RELEASE_STAGE: {
+ break;
+ }
+
+ case DELAY_STAGE:
+ case ATTACK_STAGE:
+ case DECAY_STAGE:
+ case SUSTAIN_STAGE: {
+ bool gateMode = _modeParam.value > 0.5;
+ bool holdComplete = _holdProgress >= 1.0;
+ if (!holdComplete) {
+ // run the hold accumulation even if we're not in hold mode, in case we switch mid-cycle.
+ _holdProgress += stepAmount(_holdParam, _holdInput, slow);
+ holdComplete = _holdProgress >= 1.0;
+ }
+
+ if (gateMode ? !_trigger.isHigh() : holdComplete) {
+ _stage = RELEASE_STAGE;
+ _stageProgress = 0.0;
+ _releaseLevel = _envelope;
+ }
+ break;
+ }
+ }
+ }
+
+ bool complete = false;
+ switch (_stage) {
+ case STOPPED_STAGE: {
+ break;
+ }
+
+ case DELAY_STAGE: {
+ _stageProgress += stepAmount(_delayParam, _delayInput, slow, true);
+ if (_stageProgress >= 1.0) {
+ _stage = ATTACK_STAGE;
+ _stageProgress = 0.0;
+ }
+ break;
+ }
+
+ case ATTACK_STAGE: {
+ _stageProgress += stepAmount(_attackParam, _attackInput, slow);
+ switch ((int)_attackShapeParam.value) {
+ case SHAPE2: {
+ _envelope = _stageProgress;
+ break;
+ }
+ case SHAPE3: {
+ _envelope = pow(_stageProgress, shapeExponent);
+ break;
+ }
+ default: {
+ _envelope = pow(_stageProgress, shapeInverseExponent);
+ break;
+ }
+ }
+ if (_envelope >= 1.0) {
+ _stage = DECAY_STAGE;
+ _stageProgress = 0.0;
+ }
+ break;
+ }
+
+ case DECAY_STAGE: {
+ float sustainLevel = knobAmount(_sustainParam, _sustainInput);
+ _stageProgress += stepAmount(_decayParam, _decayInput, slow);
+ switch ((int)_decayShapeParam.value) {
+ case SHAPE2: {
+ _envelope = 1.0 - _stageProgress;
+ break;
+ }
+ case SHAPE3: {
+ _envelope = _stageProgress >= 1.0 ? 0.0 : pow(1.0 - _stageProgress, shapeInverseExponent);
+ break;
+ }
+ default: {
+ _envelope = _stageProgress >= 1.0 ? 0.0 : pow(1.0 - _stageProgress, shapeExponent);
+ break;
+ }
+ }
+ _envelope *= 1.0 - sustainLevel;
+ _envelope += sustainLevel;
+ if (_envelope <= sustainLevel) {
+ _stage = SUSTAIN_STAGE;
+ }
+ break;
+ }
+
+ case SUSTAIN_STAGE: {
+ _envelope = knobAmount(_sustainParam, _sustainInput);
+ break;
+ }
+
+ case RELEASE_STAGE: {
+ _stageProgress += stepAmount(_releaseParam, _releaseInput, slow);
+ switch ((int)_releaseShapeParam.value) {
+ case SHAPE2: {
+ _envelope = 1.0 - _stageProgress;
+ break;
+ }
+ case SHAPE3: {
+ _envelope = _stageProgress >= 1.0 ? 0.0 : pow(1.0 - _stageProgress, shapeInverseExponent);
+ break;
+ }
+ default: {
+ _envelope = _stageProgress >= 1.0 ? 0.0 : pow(1.0 - _stageProgress, shapeExponent);
+ break;
+ }
+ }
+ _envelope *= _releaseLevel;
+ if (_envelope <= 0.001) {
+ complete = true;
+ _envelope = 0.0;
+ if (_loopParam.value <= 0.5) {
+ _stage = DELAY_STAGE;
+ _holdProgress = _stageProgress = 0.0;
+ }
+ else {
+ _stage = STOPPED_STAGE;
+ }
+ }
+ break;
+ }
+ }
+
+ float env = _envelope * 10.0;
+ _envOutput.value = env;
+ _invOutput.value = 10.0 - env;
+ _triggerOutput.value = complete ? 5.0 : 0.0;
+
+ if (_delayOutput) {
+ _delayOutput->value = _stage == DELAY_STAGE ? 5.0 : 0.0;
+ }
+ if (_attackOutput) {
+ _attackOutput->value = _stage == ATTACK_STAGE ? 5.0 : 0.0;
+ }
+ if (_decayOutput) {
+ _decayOutput->value = _stage == DECAY_STAGE ? 5.0 : 0.0;
+ }
+ if (_sustainOutput) {
+ _sustainOutput->value = _stage == SUSTAIN_STAGE ? 5.0 : 0.0;
+ }
+ if (_releaseOutput) {
+ _releaseOutput->value = _stage == RELEASE_STAGE ? 5.0 : 0.0;
+ }
+
+ _delayLight.value = _stage == DELAY_STAGE;
+ _attackLight.value = _stage == ATTACK_STAGE;
+ _decayLight.value = _stage == DECAY_STAGE;
+ _sustainLight.value = _stage == SUSTAIN_STAGE;
+ _releaseLight.value = _stage == RELEASE_STAGE;
+
+ _attackShape1Light.value = (int)_attackShapeParam.value == SHAPE1;
+ _attackShape2Light.value = (int)_attackShapeParam.value == SHAPE2;
+ _attackShape3Light.value = (int)_attackShapeParam.value == SHAPE3;
+ _decayShape1Light.value = (int)_decayShapeParam.value == SHAPE1;
+ _decayShape2Light.value = (int)_decayShapeParam.value == SHAPE2;
+ _decayShape3Light.value = (int)_decayShapeParam.value == SHAPE3;
+ _releaseShape1Light.value = (int)_releaseShapeParam.value == SHAPE1;
+ _releaseShape2Light.value = (int)_releaseShapeParam.value == SHAPE2;
+ _releaseShape3Light.value = (int)_releaseShapeParam.value == SHAPE3;
+}
+
+float DADSRHCore::stepAmount(const Param& knob, const Input* cv, bool slow, bool allowZero) {
+ return engineGetSampleTime() / knobTime(knob, cv, slow, allowZero);
+}
+
+float DADSRHCore::knobTime(const Param& knob, const Input* cv, bool slow, bool allowZero) {
+ float t = knobAmount(knob, cv);
+ t = pow(t, 2.0);
+ t = fmaxf(t, allowZero ? 0.0 : 0.001);
+ return t * (slow ? 100.0 : 10.0);
+}
+
+float DADSRHCore::knobAmount(const Param& knob, const Input* cv) const {
+ float v = clampf(knob.value, 0.0, 1.0);
+ if (cv && cv->active) {
+ v *= clampf(cv->value / 10.0, 0.0, 1.0);
+ }
+ return v;
+}
diff --git a/src/DADSRHCore.hpp b/src/DADSRHCore.hpp
@@ -0,0 +1,170 @@
+
+#include "dsp/digital.hpp"
+#include "BogaudioModules.hpp"
+
+struct DADSRHCore {
+ enum Stage {
+ STOPPED_STAGE,
+ DELAY_STAGE,
+ ATTACK_STAGE,
+ DECAY_STAGE,
+ SUSTAIN_STAGE,
+ RELEASE_STAGE
+ };
+
+ Param& _delayParam;
+ Param& _attackParam;
+ Param& _decayParam;
+ Param& _sustainParam;
+ Param& _releaseParam;
+ Param& _holdParam;
+ Param& _attackShapeParam;
+ Param& _decayShapeParam;
+ Param& _releaseShapeParam;
+ Param& _triggerParam;
+ Param& _modeParam;
+ Param& _loopParam;
+ Param& _speedParam;
+ Param& _retriggerParam;
+
+ Input* _delayInput;
+ Input* _attackInput;
+ Input* _decayInput;
+ Input* _sustainInput;
+ Input* _releaseInput;
+ Input* _holdInput;
+ Input& _triggerInput;
+
+ Output* _delayOutput;
+ Output* _attackOutput;
+ Output* _decayOutput;
+ Output* _sustainOutput;
+ Output* _releaseOutput;
+ Output& _envOutput;
+ Output& _invOutput;
+ Output& _triggerOutput;
+
+ Light& _delayLight;
+ Light& _attackLight;
+ Light& _decayLight;
+ Light& _sustainLight;
+ Light& _releaseLight;
+ Light& _attackShape1Light;
+ Light& _attackShape2Light;
+ Light& _attackShape3Light;
+ Light& _decayShape1Light;
+ Light& _decayShape2Light;
+ Light& _decayShape3Light;
+ Light& _releaseShape1Light;
+ Light& _releaseShape2Light;
+ Light& _releaseShape3Light;
+
+ SchmittTrigger _trigger;//, _attackShapeTrigger, _decayShapeTrigger, _releaseShapeTrigger;
+ Stage _stage;
+ float _envelope, _stageProgress, _holdProgress, _releaseLevel;
+
+ DADSRHCore(
+ Param& delayParam,
+ Param& attackParam,
+ Param& decayParam,
+ Param& sustainParam,
+ Param& releaseParam,
+ Param& holdParam,
+ Param& attackShapeParam,
+ Param& decayShapeParam,
+ Param& releaseShapeParam,
+ Param& triggerParam,
+ Param& modeParam,
+ Param& loopParam,
+ Param& speedParam,
+ Param& retriggerParam,
+
+ Input* delayInput,
+ Input* attackInput,
+ Input* decayInput,
+ Input* sustainInput,
+ Input* releaseInput,
+ Input* holdInput,
+ Input& triggerInput,
+
+ Output* delayOutput,
+ Output* attackOutput,
+ Output* decayOutput,
+ Output* sustainOutput,
+ Output* releaseOutput,
+ Output& envOutput,
+ Output& invOutput,
+ Output& triggerOutput,
+
+ Light& delayLight,
+ Light& attackLight,
+ Light& decayLight,
+ Light& sustainLight,
+ Light& releaseLight,
+ Light& attackShape1Light,
+ Light& attackShape2Light,
+ Light& attackShape3Light,
+ Light& decayShape1Light,
+ Light& decayShape2Light,
+ Light& decayShape3Light,
+ Light& releaseShape1Light,
+ Light& releaseShape2Light,
+ Light& releaseShape3Light
+ ) : _delayParam(delayParam)
+ , _attackParam(attackParam)
+ , _decayParam(decayParam)
+ , _sustainParam(sustainParam)
+ , _releaseParam(releaseParam)
+ , _holdParam(holdParam)
+ , _attackShapeParam(attackShapeParam)
+ , _decayShapeParam(decayShapeParam)
+ , _releaseShapeParam(releaseShapeParam)
+ , _triggerParam(triggerParam)
+ , _modeParam(modeParam)
+ , _loopParam(loopParam)
+ , _speedParam(speedParam)
+ , _retriggerParam(retriggerParam)
+
+ , _delayInput(delayInput)
+ , _attackInput(attackInput)
+ , _decayInput(decayInput)
+ , _sustainInput(sustainInput)
+ , _releaseInput(releaseInput)
+ , _holdInput(holdInput)
+ , _triggerInput(triggerInput)
+
+ , _delayOutput(delayOutput)
+ , _attackOutput(attackOutput)
+ , _decayOutput(decayOutput)
+ , _sustainOutput(sustainOutput)
+ , _releaseOutput(releaseOutput)
+ , _envOutput(envOutput)
+ , _invOutput(invOutput)
+ , _triggerOutput(triggerOutput)
+
+ , _delayLight(delayLight)
+ , _attackLight(attackLight)
+ , _decayLight(decayLight)
+ , _sustainLight(sustainLight)
+ , _releaseLight(releaseLight)
+ , _attackShape1Light(attackShape1Light)
+ , _attackShape2Light(attackShape2Light)
+ , _attackShape3Light(attackShape3Light)
+ , _decayShape1Light(decayShape1Light)
+ , _decayShape2Light(decayShape2Light)
+ , _decayShape3Light(decayShape3Light)
+ , _releaseShape1Light(releaseShape1Light)
+ , _releaseShape2Light(releaseShape2Light)
+ , _releaseShape3Light(releaseShape3Light)
+ {
+ reset();
+ }
+
+ void reset();
+ void step();
+
+ // float nextShaper(float shape);
+ float stepAmount(const Param& knob, const Input* cv, bool slow, bool allowZero = false);
+ float knobTime(const Param& knob, const Input* cv, bool slow, bool allowZero = false);
+ float knobAmount(const Param& knob, const Input* cv) const;
+};
diff --git a/src/DADSRHPlus.cpp b/src/DADSRHPlus.cpp
@@ -0,0 +1,244 @@
+
+#include "DADSRHCore.hpp"
+
+struct DADSRHPlus : Module {
+ enum ParamsIds {
+ DELAY_PARAM,
+ ATTACK_PARAM,
+ DECAY_PARAM,
+ SUSTAIN_PARAM,
+ RELEASE_PARAM,
+ HOLD_PARAM,
+ ATTACK_SHAPE_PARAM,
+ DECAY_SHAPE_PARAM,
+ RELEASE_SHAPE_PARAM,
+ TRIGGER_PARAM,
+ MODE_PARAM,
+ LOOP_PARAM,
+ SPEED_PARAM,
+ RETRIGGER_PARAM,
+ NUM_PARAMS
+ };
+
+ enum InputsIds {
+ DELAY_INPUT,
+ ATTACK_INPUT,
+ DECAY_INPUT,
+ SUSTAIN_INPUT,
+ RELEASE_INPUT,
+ HOLD_INPUT,
+ TRIGGER_INPUT,
+ NUM_INPUTS
+ };
+
+ enum OutputsIds {
+ DELAY_OUTPUT,
+ ATTACK_OUTPUT,
+ DECAY_OUTPUT,
+ SUSTAIN_OUTPUT,
+ RELEASE_OUTPUT,
+ ENV_OUTPUT,
+ INV_OUTPUT,
+ TRIGGER_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ enum LightsIds {
+ DELAY_LIGHT,
+ ATTACK_LIGHT,
+ DECAY_LIGHT,
+ SUSTAIN_LIGHT,
+ RELEASE_LIGHT,
+ ATTACK_SHAPE1_LIGHT,
+ ATTACK_SHAPE2_LIGHT,
+ ATTACK_SHAPE3_LIGHT,
+ DECAY_SHAPE1_LIGHT,
+ DECAY_SHAPE2_LIGHT,
+ DECAY_SHAPE3_LIGHT,
+ RELEASE_SHAPE1_LIGHT,
+ RELEASE_SHAPE2_LIGHT,
+ RELEASE_SHAPE3_LIGHT,
+ NUM_LIGHTS
+ };
+
+ DADSRHCore _core;
+
+ DADSRHPlus() : Module(
+ NUM_PARAMS,
+ NUM_INPUTS,
+ NUM_OUTPUTS,
+ NUM_LIGHTS
+ )
+ , _core(
+ params[DELAY_PARAM],
+ params[ATTACK_PARAM],
+ params[DECAY_PARAM],
+ params[SUSTAIN_PARAM],
+ params[RELEASE_PARAM],
+ params[HOLD_PARAM],
+ params[ATTACK_SHAPE_PARAM],
+ params[DECAY_SHAPE_PARAM],
+ params[RELEASE_SHAPE_PARAM],
+ params[TRIGGER_PARAM],
+ params[MODE_PARAM],
+ params[LOOP_PARAM],
+ params[SPEED_PARAM],
+ params[RETRIGGER_PARAM],
+
+ &inputs[DELAY_INPUT],
+ &inputs[ATTACK_INPUT],
+ &inputs[DECAY_INPUT],
+ &inputs[SUSTAIN_INPUT],
+ &inputs[RELEASE_INPUT],
+ &inputs[HOLD_INPUT],
+ inputs[TRIGGER_INPUT],
+
+ &outputs[DELAY_OUTPUT],
+ &outputs[ATTACK_OUTPUT],
+ &outputs[DECAY_OUTPUT],
+ &outputs[SUSTAIN_OUTPUT],
+ &outputs[RELEASE_OUTPUT],
+ outputs[ENV_OUTPUT],
+ outputs[INV_OUTPUT],
+ outputs[TRIGGER_OUTPUT],
+
+ lights[DELAY_LIGHT],
+ lights[ATTACK_LIGHT],
+ lights[DECAY_LIGHT],
+ lights[SUSTAIN_LIGHT],
+ lights[RELEASE_LIGHT],
+ lights[ATTACK_SHAPE1_LIGHT],
+ lights[ATTACK_SHAPE2_LIGHT],
+ lights[ATTACK_SHAPE3_LIGHT],
+ lights[DECAY_SHAPE1_LIGHT],
+ lights[DECAY_SHAPE2_LIGHT],
+ lights[DECAY_SHAPE3_LIGHT],
+ lights[RELEASE_SHAPE1_LIGHT],
+ lights[RELEASE_SHAPE2_LIGHT],
+ lights[RELEASE_SHAPE3_LIGHT]
+ ) {
+ reset();
+ }
+
+ virtual void reset() override {
+ _core.reset();
+ }
+
+ virtual void step() override {
+ _core.step();
+ }
+};
+
+DADSRHPlusWidget::DADSRHPlusWidget() {
+ DADSRHPlus *module = new DADSRHPlus();
+ setModule(module);
+ box.size = Vec(RACK_GRID_WIDTH * 15, RACK_GRID_HEIGHT);
+
+ {
+ SVGPanel *panel = new SVGPanel();
+ panel->box.size = box.size;
+ panel->setBackground(SVG::load(assetPlugin(plugin, "res/DADSRHPlus.svg")));
+ addChild(panel);
+ }
+
+ addChild(createScrew<ScrewSilver>(Vec(15, 0)));
+ addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 0)));
+ addChild(createScrew<ScrewSilver>(Vec(15, 365)));
+ addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 365)));
+
+ // generated by svg_widgets.rb
+ auto delayParamPosition = Vec(27.08, 33.08);
+ auto attackParamPosition = Vec(27.08, 89.08);
+ auto decayParamPosition = Vec(27.08, 145.08);
+ auto sustainParamPosition = Vec(27.08, 201.08);
+ auto releaseParamPosition = Vec(27.08, 257.08);
+ auto holdParamPosition = Vec(82.38, 313.08);
+ auto attackShapeParamPosition = Vec(77.02, 124.02);
+ auto decayShapeParamPosition = Vec(77.02, 180.52);
+ auto releaseShapeParamPosition = Vec(77.02, 292.52);
+ auto triggerParamPosition = Vec(90.04, 43.04);
+ auto modeParamPosition = Vec(119.9, 96.9);
+ auto loopParamPosition = Vec(119.9, 146.9);
+ auto speedParamPosition = Vec(19.9, 322.9);
+ auto retriggerParamPosition = Vec(54.9, 322.9);
+
+ auto delayInputPosition = Vec(152.0, 40.0);
+ auto attackInputPosition = Vec(152.0, 96.0);
+ auto decayInputPosition = Vec(152.0, 152.0);
+ auto sustainInputPosition = Vec(152.0, 208.0);
+ auto releaseInputPosition = Vec(152.0, 264.0);
+ auto holdInputPosition = Vec(152.0, 320.0);
+ auto triggerInputPosition = Vec(115.0, 40.0);
+
+ auto delayOutputPosition = Vec(189.0, 40.0);
+ auto attackOutputPosition = Vec(189.0, 96.0);
+ auto decayOutputPosition = Vec(189.0, 152.0);
+ auto sustainOutputPosition = Vec(189.0, 208.0);
+ auto releaseOutputPosition = Vec(189.0, 264.0);
+ auto envOutputPosition = Vec(115.0, 191.0);
+ auto invOutputPosition = Vec(115.0, 228.0);
+ auto triggerOutputPosition = Vec(115.0, 265.0);
+
+ auto delayLightPosition = Vec(12.0, 76.0);
+ auto attackLightPosition = Vec(12.0, 123.0);
+ auto decayLightPosition = Vec(12.0, 179.0);
+ auto sustainLightPosition = Vec(12.0, 235.0);
+ auto releaseLightPosition = Vec(12.0, 291.0);
+ auto attackShape1LightPosition = Vec(77.0, 96.0);
+ auto attackShape2LightPosition = Vec(77.0, 106.0);
+ auto attackShape3LightPosition = Vec(77.0, 116.0);
+ auto decayShape1LightPosition = Vec(77.0, 152.5);
+ auto decayShape2LightPosition = Vec(77.0, 162.5);
+ auto decayShape3LightPosition = Vec(77.0, 172.5);
+ auto releaseShape1LightPosition = Vec(77.0, 264.5);
+ auto releaseShape2LightPosition = Vec(77.0, 274.5);
+ auto releaseShape3LightPosition = Vec(77.0, 284.5);
+ // end generated by svg_widgets.rb
+
+ addParam(createParam<Knob38>(delayParamPosition, module, DADSRHPlus::DELAY_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParam<Knob38>(attackParamPosition, module, DADSRHPlus::ATTACK_PARAM, 0.0, 1.0, 0.12));
+ addParam(createParam<Knob38>(decayParamPosition, module, DADSRHPlus::DECAY_PARAM, 0.0, 1.0, 0.32));
+ addParam(createParam<Knob38>(sustainParamPosition, module, DADSRHPlus::SUSTAIN_PARAM, 0.0, 1.0, 0.5));
+ addParam(createParam<Knob38>(releaseParamPosition, module, DADSRHPlus::RELEASE_PARAM, 0.0, 1.0, 0.32));
+ addParam(createParam<Knob38>(holdParamPosition, module, DADSRHPlus::HOLD_PARAM, 0.0, 1.0, 0.45));
+ addParam(createParam<Button9Toggle3>(attackShapeParamPosition, module, DADSRHPlus::ATTACK_SHAPE_PARAM, 1.0, 3.0, 1.0));
+ addParam(createParam<Button9Toggle3>(decayShapeParamPosition, module, DADSRHPlus::DECAY_SHAPE_PARAM, 1.0, 3.0, 1.0));
+ addParam(createParam<Button9Toggle3>(releaseShapeParamPosition, module, DADSRHPlus::RELEASE_SHAPE_PARAM, 1.0, 3.0, 1.0));
+ addParam(createParam<Button18>(triggerParamPosition, module, DADSRHPlus::TRIGGER_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParam<CKSS>(modeParamPosition, module, DADSRHPlus::MODE_PARAM, 0.0, 1.0, 1.0));
+ addParam(createParam<CKSS>(loopParamPosition, module, DADSRHPlus::LOOP_PARAM, 0.0, 1.0, 1.0));
+ addParam(createParam<CKSS>(speedParamPosition, module, DADSRHPlus::SPEED_PARAM, 0.0, 1.0, 1.0));
+ addParam(createParam<CKSS>(retriggerParamPosition, module, DADSRHPlus::RETRIGGER_PARAM, 0.0, 1.0, 1.0));
+
+ addInput(createInput<PJ301MPort>(delayInputPosition, module, DADSRHPlus::DELAY_INPUT));
+ addInput(createInput<PJ301MPort>(attackInputPosition, module, DADSRHPlus::ATTACK_INPUT));
+ addInput(createInput<PJ301MPort>(decayInputPosition, module, DADSRHPlus::DECAY_INPUT));
+ addInput(createInput<PJ301MPort>(sustainInputPosition, module, DADSRHPlus::SUSTAIN_INPUT));
+ addInput(createInput<PJ301MPort>(releaseInputPosition, module, DADSRHPlus::RELEASE_INPUT));
+ addInput(createInput<PJ301MPort>(holdInputPosition, module, DADSRHPlus::HOLD_INPUT));
+ addInput(createInput<PJ301MPort>(triggerInputPosition, module, DADSRHPlus::TRIGGER_INPUT));
+
+ addOutput(createOutput<PJ301MPort>(delayOutputPosition, module, DADSRHPlus::DELAY_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(attackOutputPosition, module, DADSRHPlus::ATTACK_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(decayOutputPosition, module, DADSRHPlus::DECAY_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(sustainOutputPosition, module, DADSRHPlus::SUSTAIN_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(releaseOutputPosition, module, DADSRHPlus::RELEASE_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(envOutputPosition, module, DADSRHPlus::ENV_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(invOutputPosition, module, DADSRHPlus::INV_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(triggerOutputPosition, module, DADSRHPlus::TRIGGER_OUTPUT));
+
+ addChild(createLight<TinyLight<GreenLight>>(delayLightPosition, module, DADSRHPlus::DELAY_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(attackLightPosition, module, DADSRHPlus::ATTACK_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(decayLightPosition, module, DADSRHPlus::DECAY_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(sustainLightPosition, module, DADSRHPlus::SUSTAIN_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(releaseLightPosition, module, DADSRHPlus::RELEASE_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(attackShape1LightPosition, module, DADSRHPlus::ATTACK_SHAPE1_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(attackShape2LightPosition, module, DADSRHPlus::ATTACK_SHAPE2_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(attackShape3LightPosition, module, DADSRHPlus::ATTACK_SHAPE3_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(decayShape1LightPosition, module, DADSRHPlus::DECAY_SHAPE1_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(decayShape2LightPosition, module, DADSRHPlus::DECAY_SHAPE2_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(decayShape3LightPosition, module, DADSRHPlus::DECAY_SHAPE3_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(releaseShape1LightPosition, module, DADSRHPlus::RELEASE_SHAPE1_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(releaseShape2LightPosition, module, DADSRHPlus::RELEASE_SHAPE2_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(releaseShape3LightPosition, module, DADSRHPlus::RELEASE_SHAPE3_LIGHT));
+}
diff --git a/src/Offset.cpp b/src/Offset.cpp
@@ -0,0 +1,84 @@
+
+#include "BogaudioModules.hpp"
+
+struct Offset : Module {
+ enum ParamIds {
+ OFFSET_PARAM,
+ ATTEN_PARAM,
+ NUM_PARAMS
+ };
+
+ enum InputIds {
+ OFFSET_INPUT,
+ ATTEN_INPUT,
+ IN_INPUT,
+ NUM_INPUTS
+ };
+
+ enum OutputIds {
+ OUT_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ Offset() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {}
+
+ virtual void step() override;
+
+ float knobValue(const Param& knob, const Input& cv) const;
+};
+
+void Offset::step() {
+ float offset = knobValue(params[OFFSET_PARAM], inputs[OFFSET_INPUT]);
+ float atten = knobValue(params[ATTEN_PARAM], inputs[ATTEN_INPUT]);
+ if (inputs[IN_INPUT].active) {
+ outputs[OUT_OUTPUT].value = clampf(inputs[IN_INPUT].value + 10.0 * offset, -10.0, 10.0) * atten;
+ }
+ else {
+ outputs[OUT_OUTPUT].value = 10.0 * offset * atten;
+ }
+}
+
+float Offset::knobValue(const Param& knob, const Input& cv) const {
+ float v = clampf(knob.value, -1.0, 1.0);
+ if (cv.active) {
+ v *= clampf(cv.value / 10.0, -1.0, 1.0);
+ }
+ return v;
+}
+
+
+OffsetWidget::OffsetWidget() {
+ Offset *module = new Offset();
+ setModule(module);
+ box.size = Vec(RACK_GRID_WIDTH * 3, RACK_GRID_HEIGHT);
+
+ {
+ SVGPanel *panel = new SVGPanel();
+ panel->box.size = box.size;
+ panel->setBackground(SVG::load(assetPlugin(plugin, "res/Offset.svg")));
+ addChild(panel);
+ }
+
+ addChild(createScrew<ScrewSilver>(Vec(0, 0)));
+ addChild(createScrew<ScrewSilver>(Vec(box.size.x - 15, 365)));
+
+ // generated by svg_widgets.rb
+ auto offsetParamPosition = Vec(7.5, 39.5);
+ auto attenParamPosition = Vec(7.5, 151.5);
+
+ auto offsetInputPosition = Vec(10.5, 81.0);
+ auto attenInputPosition = Vec(10.5, 193.0);
+ auto inInputPosition = Vec(10.5, 243.0);
+
+ auto outOutputPosition = Vec(10.5, 281.0);
+ // end generated by svg_widgets.rb
+
+ addParam(createParam<Knob29>(offsetParamPosition, module, Offset::OFFSET_PARAM, -1.0, 1.0, 0.0));
+ addParam(createParam<Knob29>(attenParamPosition, module, Offset::ATTEN_PARAM, -1.0, 1.0, 0.0));
+
+ addInput(createInput<PJ301MPort>(offsetInputPosition, module, Offset::OFFSET_INPUT));
+ addInput(createInput<PJ301MPort>(attenInputPosition, module, Offset::ATTEN_INPUT));
+ addInput(createInput<PJ301MPort>(inInputPosition, module, Offset::IN_INPUT));
+
+ addOutput(createOutput<PJ301MPort>(outOutputPosition, module, Offset::OUT_OUTPUT));
+}
diff --git a/src/SampleHold.cpp b/src/SampleHold.cpp
@@ -0,0 +1,129 @@
+
+#include "dsp/digital.hpp"
+#include "BogaudioModules.hpp"
+
+struct SampleHold : Module {
+ enum ParamIds {
+ TRIGGER1_PARAM,
+ TRIGGER2_PARAM,
+ NUM_PARAMS
+ };
+
+ enum InputIds {
+ TRIGGER1_INPUT,
+ IN1_INPUT,
+ TRIGGER2_INPUT,
+ IN2_INPUT,
+ NUM_INPUTS
+ };
+
+ enum OutputIds {
+ OUT1_OUTPUT,
+ OUT2_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ SchmittTrigger _trigger1, _trigger2;
+ float _value1, _value2;
+
+ SampleHold() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {
+ reset();
+ }
+
+ virtual void reset() override;
+ virtual void step() override;
+ void step(
+ Param& triggerParam,
+ Input& triggerInput,
+ Input& in,
+ Output& out,
+ SchmittTrigger& trigger,
+ float& value
+ );
+};
+
+void SampleHold::reset() {
+ _trigger1.reset();
+ _value1 = 0.0;
+ _trigger2.reset();
+ _value1 = 0.0;
+}
+
+void SampleHold::step() {
+ step(
+ params[TRIGGER1_PARAM],
+ inputs[TRIGGER1_INPUT],
+ inputs[IN1_INPUT],
+ outputs[OUT1_OUTPUT],
+ _trigger1,
+ _value1
+ );
+
+ step(
+ params[TRIGGER2_PARAM],
+ inputs[TRIGGER2_INPUT],
+ inputs[IN2_INPUT],
+ outputs[OUT2_OUTPUT],
+ _trigger2,
+ _value2
+ );
+}
+
+void SampleHold::step(
+ Param& triggerParam,
+ Input& triggerInput,
+ Input& in,
+ Output& out,
+ SchmittTrigger& trigger,
+ float& value
+) {
+ if (trigger.process(triggerParam.value + triggerInput.value)) {
+ if (in.active) {
+ value = in.value;
+ }
+ else {
+ value = randomf() * 10.0;
+ }
+ }
+ out.value = value;
+}
+
+SampleHoldWidget::SampleHoldWidget() {
+ SampleHold *module = new SampleHold();
+ setModule(module);
+ box.size = Vec(RACK_GRID_WIDTH * 3, RACK_GRID_HEIGHT);
+
+ {
+ SVGPanel *panel = new SVGPanel();
+ panel->box.size = box.size;
+ panel->setBackground(SVG::load(assetPlugin(plugin, "res/SampleHold.svg")));
+ addChild(panel);
+ }
+
+ addChild(createScrew<ScrewSilver>(Vec(0, 0)));
+ addChild(createScrew<ScrewSilver>(Vec(box.size.x - 15, 365)));
+
+ // generated by svg_widgets.rb
+ auto trigger1ParamPosition = Vec(13.5, 33.0);
+ auto trigger2ParamPosition = Vec(13.5, 193.0);
+
+ auto trigger1InputPosition = Vec(10.5, 56.0);
+ auto in1InputPosition = Vec(10.5, 93.0);
+ auto trigger2InputPosition = Vec(10.5, 216.0);
+ auto in2InputPosition = Vec(10.5, 253.0);
+
+ auto out1OutputPosition = Vec(10.5, 131.0);
+ auto out2OutputPosition = Vec(10.5, 291.0);
+ // end generated by svg_widgets.rb
+
+ addParam(createParam<Button18>(trigger1ParamPosition, module, SampleHold::TRIGGER1_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParam<Button18>(trigger2ParamPosition, module, SampleHold::TRIGGER2_PARAM, 0.0, 1.0, 0.0));
+
+ addInput(createInput<PJ301MPort>(trigger1InputPosition, module, SampleHold::TRIGGER1_INPUT));
+ addInput(createInput<PJ301MPort>(in1InputPosition, module, SampleHold::IN1_INPUT));
+ addInput(createInput<PJ301MPort>(trigger2InputPosition, module, SampleHold::TRIGGER2_INPUT));
+ addInput(createInput<PJ301MPort>(in2InputPosition, module, SampleHold::IN2_INPUT));
+
+ addOutput(createOutput<PJ301MPort>(out1OutputPosition, module, SampleHold::OUT1_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(out2OutputPosition, module, SampleHold::OUT2_OUTPUT));
+}
diff --git a/src/Shaper.cpp b/src/Shaper.cpp
@@ -0,0 +1,161 @@
+#include "ShaperCore.hpp"
+
+struct Shaper : Module {
+ enum ParamIds {
+ ATTACK_PARAM,
+ ON_PARAM,
+ DECAY_PARAM,
+ OFF_PARAM,
+ ENV_PARAM,
+ SIGNAL_PARAM,
+ TRIGGER_PARAM,
+ SPEED_PARAM,
+ LOOP_PARAM,
+ NUM_PARAMS
+ };
+
+ enum InputIds {
+ SIGNAL_INPUT,
+ TRIGGER_INPUT,
+ NUM_INPUTS
+ };
+
+ enum OutputIds {
+ SIGNAL_OUTPUT,
+ ENV_OUTPUT,
+ INV_OUTPUT,
+ TRIGGER_OUTPUT,
+ NUM_OUTPUTS
+ };
+
+ enum LightIds {
+ ATTACK_LIGHT,
+ ON_LIGHT,
+ DECAY_LIGHT,
+ OFF_LIGHT,
+ NUM_LIGHTS
+ };
+
+ ShaperCore _core;
+
+ Shaper() : Module(
+ NUM_PARAMS,
+ NUM_INPUTS,
+ NUM_OUTPUTS,
+ NUM_LIGHTS
+ )
+ , _core(
+ params[ATTACK_PARAM],
+ params[ON_PARAM],
+ params[DECAY_PARAM],
+ params[OFF_PARAM],
+ params[ENV_PARAM],
+ params[SIGNAL_PARAM],
+ params[TRIGGER_PARAM],
+ params[SPEED_PARAM],
+ params[LOOP_PARAM],
+
+ inputs[SIGNAL_INPUT],
+ inputs[TRIGGER_INPUT],
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+
+ outputs[SIGNAL_OUTPUT],
+ outputs[ENV_OUTPUT],
+ outputs[INV_OUTPUT],
+ outputs[TRIGGER_OUTPUT],
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+
+ lights[ATTACK_LIGHT],
+ lights[ON_LIGHT],
+ lights[DECAY_LIGHT],
+ lights[OFF_LIGHT]
+ )
+ {
+ reset();
+ }
+
+ virtual void reset() override {
+ _core.reset();
+ }
+
+ virtual void step() override {
+ _core.step();
+ }
+};
+
+
+ShaperWidget::ShaperWidget() {
+ Shaper *module = new Shaper();
+ setModule(module);
+ box.size = Vec(RACK_GRID_WIDTH * 10, RACK_GRID_HEIGHT);
+
+ {
+ SVGPanel *panel = new SVGPanel();
+ panel->box.size = box.size;
+ panel->setBackground(SVG::load(assetPlugin(plugin, "res/Shaper.svg")));
+ addChild(panel);
+ }
+
+ addChild(createScrew<ScrewSilver>(Vec(0, 0)));
+ addChild(createScrew<ScrewSilver>(Vec(box.size.x - 15, 0)));
+ addChild(createScrew<ScrewSilver>(Vec(0, 365)));
+ addChild(createScrew<ScrewSilver>(Vec(box.size.x - 15, 365)));
+
+ // generated by svg_widgets.rb
+ auto attackParamPosition = Vec(29.08, 33.08);
+ auto triggerParamPosition = Vec(89.04, 43.04);
+ auto onParamPosition = Vec(29.08, 89.08);
+ auto speedParamPosition = Vec(118.9, 97.9);
+ auto decayParamPosition = Vec(29.08, 145.08);
+ auto loopParamPosition = Vec(118.9, 153.9);
+ auto offParamPosition = Vec(29.08, 201.08);
+ auto envParamPosition = Vec(82.38, 257.08);
+ auto signalParamPosition = Vec(82.38, 313.08);
+
+ auto triggerInputPosition = Vec(114.0, 40.0);
+ auto signalInputPosition = Vec(11.5, 320.0);
+
+ auto triggerOutputPosition = Vec(114.0, 208.0);
+ auto envOutputPosition = Vec(11.5, 264.0);
+ auto invOutputPosition = Vec(40.5, 264.0);
+ auto signalOutputPosition = Vec(40.5, 320.0);
+
+ auto attackLightPosition = Vec(12.0, 80.0);
+ auto onLightPosition = Vec(12.0, 121.0);
+ auto decayLightPosition = Vec(12.0, 189.0);
+ auto offLightPosition = Vec(12.0, 237.0);
+ // end generated by svg_widgets.rb
+
+ addParam(createParam<Knob38>(attackParamPosition, module, Shaper::ATTACK_PARAM, 0.0, 1.0, 0.12));
+ addParam(createParam<Knob38>(onParamPosition, module, Shaper::ON_PARAM, 0.0, 1.0, 0.32));
+ addParam(createParam<Knob38>(decayParamPosition, module, Shaper::DECAY_PARAM, 0.0, 1.0, 0.32));
+ addParam(createParam<Knob38>(offParamPosition, module, Shaper::OFF_PARAM, 0.0, 1.0, 0.07));
+ addParam(createParam<Knob38>(envParamPosition, module, Shaper::ENV_PARAM, 0.0, 1.0, 1.0));
+ addParam(createParam<Knob38>(signalParamPosition, module, Shaper::SIGNAL_PARAM, 0.0, 1.0, 0.5));
+
+ addParam(createParam<Button18>(triggerParamPosition, module, Shaper::TRIGGER_PARAM, 0.0, 1.0, 0.0));
+ addInput(createInput<PJ301MPort>(triggerInputPosition, module, Shaper::TRIGGER_INPUT));
+
+ addParam(createParam<CKSS>(speedParamPosition, module, Shaper::SPEED_PARAM, 0.0, 1.0, 1.0));
+ addParam(createParam<CKSS>(loopParamPosition, module, Shaper::LOOP_PARAM, 0.0, 1.0, 1.0));
+ addOutput(createOutput<PJ301MPort>(triggerOutputPosition, module, Shaper::TRIGGER_OUTPUT));
+
+ addOutput(createOutput<PJ301MPort>(envOutputPosition, module, Shaper::ENV_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(invOutputPosition, module, Shaper::INV_OUTPUT));
+
+ addInput(createInput<PJ301MPort>(signalInputPosition, module, Shaper::SIGNAL_INPUT));
+ addOutput(createOutput<PJ301MPort>(signalOutputPosition, module, Shaper::SIGNAL_OUTPUT));
+
+ addChild(createLight<TinyLight<GreenLight>>(attackLightPosition, module, Shaper::ATTACK_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(onLightPosition, module, Shaper::ON_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(decayLightPosition, module, Shaper::DECAY_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(offLightPosition, module, Shaper::OFF_LIGHT));
+}
diff --git a/src/ShaperCore.cpp b/src/ShaperCore.cpp
@@ -0,0 +1,125 @@
+
+#include <math.h>
+#include "ShaperCore.hpp"
+
+void ShaperCore::reset() {
+ _trigger.reset();
+ _stage = STOPPED_STAGE;
+ _stageProgress = 0.0;
+}
+
+void ShaperCore::step() {
+ bool complete = false;
+ bool slow = _speedParam.value <= 0.0;
+ if (_trigger.process(_triggerParam.value + _triggerInput.value)) {
+ _stage = ATTACK_STAGE;
+ _stageProgress = 0.0;
+ }
+ else {
+ switch (_stage) {
+ case STOPPED_STAGE: {
+ break;
+ }
+ case ATTACK_STAGE: {
+ if (stepStage(_attackParam, _attackInput, slow)) {
+ _stage = ON_STAGE;
+ _stageProgress = 0.0;
+ }
+ break;
+ }
+ case ON_STAGE: {
+ if (stepStage(_onParam, _onInput, slow)) {
+ _stage = DECAY_STAGE;
+ _stageProgress = 0.0;
+ }
+ break;
+ }
+ case DECAY_STAGE: {
+ if (stepStage(_decayParam, _decayInput, slow)) {
+ _stage = OFF_STAGE;
+ _stageProgress = 0.0;
+ }
+ break;
+ }
+ case OFF_STAGE: {
+ if (stepStage(_offParam, _offInput, slow)) {
+ complete = true;
+ if (_loopParam.value <= 0.0) {
+ _stage = ATTACK_STAGE;
+ _stageProgress = 0.0;
+ }
+ else {
+ _stage = STOPPED_STAGE;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ float envelope = 0.0;
+ switch (_stage) {
+ case STOPPED_STAGE: {
+ break;
+ }
+ case ATTACK_STAGE: {
+ envelope = _stageProgress * 10.0;
+ break;
+ }
+ case ON_STAGE: {
+ envelope = 10.0;
+ break;
+ }
+ case DECAY_STAGE: {
+ envelope = (1.0 - _stageProgress) * 10.0;
+ break;
+ }
+ case OFF_STAGE: {
+ break;
+ }
+ }
+
+ float signalLevel = levelParam(_signalParam, _signalCVInput);
+ _signalOutput.value = signalLevel * envelope * _signalInput.normalize(0.0);
+
+ float envLevel = levelParam(_envParam, _envInput);
+ float envOutput = clampf(envLevel * envelope, 0.0, 10.0);
+ _envOutput.value = envOutput;
+ _invOutput.value = 10.0 - envOutput;
+ _triggerOutput.value = complete ? 5.0 : 0.0;
+
+ if (_attackOutput) {
+ _attackOutput->value = _stage == ATTACK_STAGE ? 5.0 : 0.0;
+ }
+ if (_onOutput) {
+ _onOutput->value = _stage == ON_STAGE ? 5.0 : 0.0;
+ }
+ if (_decayOutput) {
+ _decayOutput->value = _stage == DECAY_STAGE ? 5.0 : 0.0;
+ }
+ if (_offOutput) {
+ _offOutput->value = _stage == OFF_STAGE ? 5.0 : 0.0;
+ }
+
+ _attackLight.value = _stage == ATTACK_STAGE;
+ _onLight.value = _stage == ON_STAGE;
+ _decayLight.value = _stage == DECAY_STAGE;
+ _offLight.value = _stage == OFF_STAGE;
+}
+
+bool ShaperCore::stepStage(const Param& knob, const Input* cv, bool slow) {
+ float t = levelParam(knob, cv);
+ t = pow(t, 2);
+ t = fmaxf(t, 0.001);
+ t *= slow ? 100.0 : 10.0;
+ _stageProgress += engineGetSampleTime() / t;
+ return _stageProgress > 1.0;
+}
+
+float ShaperCore::levelParam(const Param& knob, const Input* cv) const {
+ float v = clampf(knob.value, 0.0, 1.0);
+ if (cv && cv->active) {
+ v *= clampf(cv->value / 10.0, 0.0, 1.0);
+ }
+ return v;
+}
diff --git a/src/ShaperCore.hpp b/src/ShaperCore.hpp
@@ -0,0 +1,124 @@
+#include "dsp/digital.hpp"
+#include "BogaudioModules.hpp"
+
+struct ShaperCore {
+ enum Stage {
+ STOPPED_STAGE,
+ ATTACK_STAGE,
+ ON_STAGE,
+ DECAY_STAGE,
+ OFF_STAGE
+ };
+
+ Param& _attackParam;
+ Param& _onParam;
+ Param& _decayParam;
+ Param& _offParam;
+ Param& _envParam;
+ Param& _signalParam;
+ Param& _triggerParam;
+ Param& _speedParam;
+ Param& _loopParam;
+
+ Input& _signalInput;
+ Input& _triggerInput;
+ Input* _attackInput;
+ Input* _onInput;
+ Input* _decayInput;
+ Input* _offInput;
+ Input* _envInput;
+ Input* _signalCVInput;
+
+ Output& _signalOutput;
+ Output& _envOutput;
+ Output& _invOutput;
+ Output& _triggerOutput;
+ Output* _attackOutput;
+ Output* _onOutput;
+ Output* _decayOutput;
+ Output* _offOutput;
+
+ Light& _attackLight;
+ Light& _onLight;
+ Light& _decayLight;
+ Light& _offLight;
+
+ SchmittTrigger _trigger;
+ Stage _stage;
+ float _stageProgress;
+
+ ShaperCore(
+ Param& attackParam,
+ Param& onParam,
+ Param& decayParam,
+ Param& offParam,
+ Param& envParam,
+ Param& signalParam,
+ Param& triggerParam,
+ Param& speedParam,
+ Param& loopParam,
+
+ Input& signalInput,
+ Input& triggerInput,
+ Input* attackInput,
+ Input* onInput,
+ Input* decayInput,
+ Input* offInput,
+ Input* envInput,
+ Input* signalCVInput,
+
+ Output& signalOutput,
+ Output& envOutput,
+ Output& invOutput,
+ Output& triggerOutput,
+ Output* attackOutput,
+ Output* onOutput,
+ Output* decayOutput,
+ Output* offOutput,
+
+ Light& attackLight,
+ Light& onLight,
+ Light& decayLight,
+ Light& offLight
+ ) : _attackParam(attackParam)
+ , _onParam(onParam)
+ , _decayParam(decayParam)
+ , _offParam(offParam)
+ , _envParam(envParam)
+ , _signalParam(signalParam)
+ , _triggerParam(triggerParam)
+ , _speedParam(speedParam)
+ , _loopParam(loopParam)
+
+ , _signalInput(signalInput)
+ , _triggerInput(triggerInput)
+ , _attackInput(attackInput)
+ , _onInput(onInput)
+ , _decayInput(decayInput)
+ , _offInput(offInput)
+ , _envInput(envInput)
+ , _signalCVInput(signalCVInput)
+
+ , _signalOutput(signalOutput)
+ , _envOutput(envOutput)
+ , _invOutput(invOutput)
+ , _triggerOutput(triggerOutput)
+ , _attackOutput(attackOutput)
+ , _onOutput(onOutput)
+ , _decayOutput(decayOutput)
+ , _offOutput(offOutput)
+
+ , _attackLight(attackLight)
+ , _onLight(onLight)
+ , _decayLight(decayLight)
+ , _offLight(offLight)
+ {
+ reset();
+ }
+
+ void reset();
+ void step();
+
+ bool stepStage(const Param& knob, const Input* cv, bool slow);
+ float levelParam(const Param& knob, const Input* cv) const;
+};
diff --git a/src/ShaperPlus.cpp b/src/ShaperPlus.cpp
@@ -0,0 +1,197 @@
+#include "ShaperCore.hpp"
+
+struct ShaperPlus : Module {
+ enum ParamIds {
+ ATTACK_PARAM,
+ ON_PARAM,
+ DECAY_PARAM,
+ OFF_PARAM,
+ ENV_PARAM,
+ SIGNAL_PARAM,
+ TRIGGER_PARAM,
+ SPEED_PARAM,
+ LOOP_PARAM,
+ NUM_PARAMS
+ };
+ enum InputIds {
+ SIGNAL_INPUT,
+ TRIGGER_INPUT,
+ ATTACK_INPUT,
+ ON_INPUT,
+ DECAY_INPUT,
+ OFF_INPUT,
+ ENV_INPUT,
+ SIGNALCV_INPUT,
+ NUM_INPUTS
+ };
+ enum OutputIds {
+ SIGNAL_OUTPUT,
+ ENV_OUTPUT,
+ INV_OUTPUT,
+ TRIGGER_OUTPUT,
+ ATTACK_OUTPUT,
+ ON_OUTPUT,
+ DECAY_OUTPUT,
+ OFF_OUTPUT,
+ NUM_OUTPUTS
+ };
+ enum LightIds {
+ ATTACK_LIGHT,
+ ON_LIGHT,
+ DECAY_LIGHT,
+ OFF_LIGHT,
+ NUM_LIGHTS
+ };
+
+ enum Stage {
+ STOPPED_STAGE,
+ ATTACK_STAGE,
+ ON_STAGE,
+ DECAY_STAGE,
+ OFF_STAGE
+ };
+
+ ShaperCore _core;
+
+ ShaperPlus() : Module(
+ NUM_PARAMS,
+ NUM_INPUTS,
+ NUM_OUTPUTS,
+ NUM_LIGHTS
+ )
+ , _core(
+ params[ATTACK_PARAM],
+ params[ON_PARAM],
+ params[DECAY_PARAM],
+ params[OFF_PARAM],
+ params[ENV_PARAM],
+ params[SIGNAL_PARAM],
+ params[TRIGGER_PARAM],
+ params[SPEED_PARAM],
+ params[LOOP_PARAM],
+
+ inputs[SIGNAL_INPUT],
+ inputs[TRIGGER_INPUT],
+ &inputs[ATTACK_INPUT],
+ &inputs[ON_INPUT],
+ &inputs[DECAY_INPUT],
+ &inputs[OFF_INPUT],
+ &inputs[ENV_INPUT],
+ &inputs[SIGNALCV_INPUT],
+
+ outputs[SIGNAL_OUTPUT],
+ outputs[ENV_OUTPUT],
+ outputs[INV_OUTPUT],
+ outputs[TRIGGER_OUTPUT],
+ &outputs[ATTACK_OUTPUT],
+ &outputs[ON_OUTPUT],
+ &outputs[DECAY_OUTPUT],
+ &outputs[OFF_OUTPUT],
+
+ lights[ATTACK_LIGHT],
+ lights[ON_LIGHT],
+ lights[DECAY_LIGHT],
+ lights[OFF_LIGHT]
+ )
+ {
+ reset();
+ }
+
+ virtual void reset() override {
+ _core.reset();
+ }
+
+ virtual void step() override {
+ _core.step();
+ }
+};
+
+ShaperPlusWidget::ShaperPlusWidget() {
+ ShaperPlus *module = new ShaperPlus();
+ setModule(module);
+ box.size = Vec(RACK_GRID_WIDTH * 15, RACK_GRID_HEIGHT);
+
+ {
+ SVGPanel *panel = new SVGPanel();
+ panel->box.size = box.size;
+ panel->setBackground(SVG::load(assetPlugin(plugin, "res/ShaperPlus.svg")));
+ addChild(panel);
+ }
+
+ addChild(createScrew<ScrewSilver>(Vec(15, 0)));
+ addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 0)));
+ addChild(createScrew<ScrewSilver>(Vec(15, 365)));
+ addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 365)));
+
+ // generated by svg_widgets.rb
+ auto attackParamPosition = Vec(29.08, 33.08);
+ auto triggerParamPosition = Vec(89.04, 43.04);
+ auto onParamPosition = Vec(29.08, 89.08);
+ auto speedParamPosition = Vec(118.9, 97.9);
+ auto decayParamPosition = Vec(29.08, 145.08);
+ auto loopParamPosition = Vec(118.9, 153.9);
+ auto offParamPosition = Vec(29.08, 201.08);
+ auto envParamPosition = Vec(82.38, 257.08);
+ auto signalParamPosition = Vec(82.38, 313.08);
+
+ auto triggerInputPosition = Vec(114.0, 40.0);
+ auto attackInputPosition = Vec(152.0, 40.0);
+ auto onInputPosition = Vec(152.0, 96.0);
+ auto decayInputPosition = Vec(152.0, 152.0);
+ auto offInputPosition = Vec(152.0, 208.0);
+ auto envInputPosition = Vec(152.0, 264.0);
+ auto signalInputPosition = Vec(11.5, 320.0);
+ auto signalcvInputPosition = Vec(152.0, 320.0);
+
+ auto attackOutputPosition = Vec(189.0, 40.0);
+ auto onOutputPosition = Vec(189.0, 96.0);
+ auto decayOutputPosition = Vec(189.0, 152.0);
+ auto offOutputPosition = Vec(189.0, 208.0);
+ auto envOutputPosition = Vec(11.5, 264.0);
+ auto invOutputPosition = Vec(40.5, 264.0);
+ auto triggerOutputPosition = Vec(189.0, 264.0);
+ auto signalOutputPosition = Vec(40.5, 320.0);
+
+ auto attackLightPosition = Vec(12.0, 80.0);
+ auto onLightPosition = Vec(12.0, 121.0);
+ auto decayLightPosition = Vec(12.0, 189.0);
+ auto offLightPosition = Vec(12.0, 237.0);
+ // end generated by svg_widgets.rb
+
+ addParam(createParam<Knob38>(attackParamPosition, module, ShaperPlus::ATTACK_PARAM, 0.0, 1.0, 0.12));
+ addParam(createParam<Knob38>(onParamPosition, module, ShaperPlus::ON_PARAM, 0.0, 1.0, 0.32));
+ addParam(createParam<Knob38>(decayParamPosition, module, ShaperPlus::DECAY_PARAM, 0.0, 1.0, 0.32));
+ addParam(createParam<Knob38>(offParamPosition, module, ShaperPlus::OFF_PARAM, 0.0, 1.0, 0.07));
+ addParam(createParam<Knob38>(envParamPosition, module, ShaperPlus::ENV_PARAM, 0.0, 1.0, 1.0));
+ addParam(createParam<Knob38>(signalParamPosition, module, ShaperPlus::SIGNAL_PARAM, 0.0, 1.0, 0.5));
+
+ addParam(createParam<Button18>(triggerParamPosition, module, ShaperPlus::TRIGGER_PARAM, 0.0, 1.0, 0.0));
+ addInput(createInput<PJ301MPort>(triggerInputPosition, module, ShaperPlus::TRIGGER_INPUT));
+
+ addParam(createParam<CKSS>(speedParamPosition, module, ShaperPlus::SPEED_PARAM, 0.0, 1.0, 1.0));
+ addParam(createParam<CKSS>(loopParamPosition, module, ShaperPlus::LOOP_PARAM, 0.0, 1.0, 1.0));
+ addOutput(createOutput<PJ301MPort>(triggerOutputPosition, module, ShaperPlus::TRIGGER_OUTPUT));
+
+ addOutput(createOutput<PJ301MPort>(envOutputPosition, module, ShaperPlus::ENV_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(invOutputPosition, module, ShaperPlus::INV_OUTPUT));
+
+ addInput(createInput<PJ301MPort>(signalInputPosition, module, ShaperPlus::SIGNAL_INPUT));
+ addOutput(createOutput<PJ301MPort>(signalOutputPosition, module, ShaperPlus::SIGNAL_OUTPUT));
+
+ addInput(createInput<PJ301MPort>(attackInputPosition, module, ShaperPlus::ATTACK_INPUT));
+ addInput(createInput<PJ301MPort>(onInputPosition, module, ShaperPlus::ON_INPUT));
+ addInput(createInput<PJ301MPort>(decayInputPosition, module, ShaperPlus::DECAY_INPUT));
+ addInput(createInput<PJ301MPort>(offInputPosition, module, ShaperPlus::OFF_INPUT));
+ addInput(createInput<PJ301MPort>(envInputPosition, module, ShaperPlus::ENV_INPUT));
+ addInput(createInput<PJ301MPort>(signalcvInputPosition, module, ShaperPlus::SIGNALCV_INPUT));
+
+ addOutput(createOutput<PJ301MPort>(attackOutputPosition, module, ShaperPlus::ATTACK_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(onOutputPosition, module, ShaperPlus::ON_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(decayOutputPosition, module, ShaperPlus::DECAY_OUTPUT));
+ addOutput(createOutput<PJ301MPort>(offOutputPosition, module, ShaperPlus::OFF_OUTPUT));
+
+ addChild(createLight<TinyLight<GreenLight>>(attackLightPosition, module, ShaperPlus::ATTACK_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(onLightPosition, module, ShaperPlus::ON_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(decayLightPosition, module, ShaperPlus::DECAY_LIGHT));
+ addChild(createLight<TinyLight<GreenLight>>(offLightPosition, module, ShaperPlus::OFF_LIGHT));
+}