BogaudioModules

BogaudioModules for VCV Rack
Log | Files | Refs | README | LICENSE

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:
AMakefile | 11+++++++++++
MREADME.md | 62+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Ares/DADSRH-src.svg | 0
Ares/DADSRH.svg | 0
Ares/DADSRHPlus-src.svg | 0
Ares/DADSRHPlus.svg | 0
Ares/Offset-src.svg | 0
Ares/Offset.svg | 0
Ares/SampleHold-src.svg | 0
Ares/SampleHold.svg | 0
Ares/Shaper-src.svg | 0
Ares/Shaper.svg | 0
Ares/ShaperPlus-src.svg | 0
Ares/ShaperPlus.svg | 0
Ares/button_18px-src.svg | 0
Ares/button_18px.svg | 0
Ares/button_9px-src.svg | 0
Ares/button_9px.svg | 0
Ares/knob_29px-src.svg | 0
Ares/knob_29px.svg | 0
Ares/knob_38px-src.svg | 0
Ares/knob_38px.svg | 0
Ares/www/modules.png | 0
Ascripts/svg_render.sh | 26++++++++++++++++++++++++++
Ascripts/svg_widgets.rb | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/BogaudioModules.cpp | 17+++++++++++++++++
Asrc/BogaudioModules.hpp | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/DADSRH.cpp | 212+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/DADSRHCore.cpp | 255+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/DADSRHCore.hpp | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/DADSRHPlus.cpp | 244+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Offset.cpp | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/SampleHold.cpp | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Shaper.cpp | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ShaperCore.cpp | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ShaperCore.hpp | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ShaperPlus.cpp | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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. + +![Modules screenshot](./res/www/modules.png) + +## 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)); +}