···11+[package]
22+name = "shapemaker_vst"
33+version = "0.1.0"
44+edition = "2021"
55+authors = ["Ewen Le Bihan <hey@ewen.works>"]
66+license = "GPL-3.0-or-later"
77+homepage = "https://ewen.works/shapemaker"
88+description = "A VST plugin for Shapemaker, an experimental audiovisual SVG-based rendering engine"
99+1010+[workspace]
1111+members = ["xtask"]
1212+1313+[lib]
1414+crate-type = ["cdylib"]
1515+1616+[dependencies]
1717+# Remove the `assert_process_allocs` feature to allow allocations on the audio
1818+# thread in debug builds.
1919+nih_plug = { git = "https://github.com/robbert-vdh/nih-plug.git", features = ["assert_process_allocs"] }
2020+# Uncomment the below line to disable the on-by-default VST3 feature to remove
2121+# the GPL compatibility requirement
2222+# nih_plug = { git = "https://github.com/robbert-vdh/nih-plug.git", default_features = false, features = ["assert_process_allocs"] }
2323+2424+[profile.release]
2525+lto = "thin"
2626+strip = "symbols"
2727+2828+[profile.profiling]
2929+inherits = "release"
3030+debug = true
3131+strip = "none"
+9
vst/README.md
···11+# Shapemaker VST
22+33+## Building
44+55+After installing [Rust](https://rustup.rs/), you can compile Shapemaker VST as follows:
66+77+```shell
88+cargo xtask bundle shapemaker_vst --release
99+```
+8
vst/bundler.toml
···11+# This provides metadata for NIH-plug's `cargo xtask bundle <foo>` plugin
22+# bundler. This file's syntax is as follows:
33+#
44+# [package_name]
55+# name = "Human Readable Plugin Name" # defaults to <package_name>
66+77+[shapemaker_vst]
88+name = "Shapemaker VST"
+157
vst/src/lib.rs
···11+use nih_plug::prelude::*;
22+use std::sync::Arc;
33+44+// This is a shortened version of the gain example with most comments removed, check out
55+// https://github.com/robbert-vdh/nih-plug/blob/master/plugins/examples/gain/src/lib.rs to get
66+// started
77+88+struct ShapemakerVST {
99+ params: Arc<ShapemakerVSTParams>,
1010+}
1111+1212+#[derive(Params)]
1313+struct ShapemakerVSTParams {
1414+ /// The parameter's ID is used to identify the parameter in the wrappred plugin API. As long as
1515+ /// these IDs remain constant, you can rename and reorder these fields as you wish. The
1616+ /// parameters are exposed to the host in the same order they were defined. In this case, this
1717+ /// gain parameter is stored as linear gain while the values are displayed in decibels.
1818+ #[id = "gain"]
1919+ pub gain: FloatParam,
2020+}
2121+2222+impl Default for ShapemakerVST {
2323+ fn default() -> Self {
2424+ Self {
2525+ params: Arc::new(ShapemakerVSTParams::default()),
2626+ }
2727+ }
2828+}
2929+3030+impl Default for ShapemakerVSTParams {
3131+ fn default() -> Self {
3232+ Self {
3333+ // This gain is stored as linear gain. NIH-plug comes with useful conversion functions
3434+ // to treat these kinds of parameters as if we were dealing with decibels. Storing this
3535+ // as decibels is easier to work with, but requires a conversion for every sample.
3636+ gain: FloatParam::new(
3737+ "Gain",
3838+ util::db_to_gain(0.0),
3939+ FloatRange::Skewed {
4040+ min: util::db_to_gain(-30.0),
4141+ max: util::db_to_gain(30.0),
4242+ // This makes the range appear as if it was linear when displaying the values as
4343+ // decibels
4444+ factor: FloatRange::gain_skew_factor(-30.0, 30.0),
4545+ },
4646+ )
4747+ // Because the gain parameter is stored as linear gain instead of storing the value as
4848+ // decibels, we need logarithmic smoothing
4949+ .with_smoother(SmoothingStyle::Logarithmic(50.0))
5050+ .with_unit(" dB")
5151+ // There are many predefined formatters we can use here. If the gain was stored as
5252+ // decibels instead of as a linear gain value, we could have also used the
5353+ // `.with_step_size(0.1)` function to get internal rounding.
5454+ .with_value_to_string(formatters::v2s_f32_gain_to_db(2))
5555+ .with_string_to_value(formatters::s2v_f32_gain_to_db()),
5656+ }
5757+ }
5858+}
5959+6060+impl Plugin for ShapemakerVST {
6161+ const NAME: &'static str = "Shapemaker VST";
6262+ const VENDOR: &'static str = "Ewen Le Bihan";
6363+ const URL: &'static str = env!("CARGO_PKG_HOMEPAGE");
6464+ const EMAIL: &'static str = "hey@ewen.works";
6565+6666+ const VERSION: &'static str = env!("CARGO_PKG_VERSION");
6767+6868+ // The first audio IO layout is used as the default. The other layouts may be selected either
6969+ // explicitly or automatically by the host or the user depending on the plugin API/backend.
7070+ const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout {
7171+ main_input_channels: NonZeroU32::new(2),
7272+ main_output_channels: NonZeroU32::new(2),
7373+7474+ aux_input_ports: &[],
7575+ aux_output_ports: &[],
7676+7777+ // Individual ports and the layout as a whole can be named here. By default these names
7878+ // are generated as needed. This layout will be called 'Stereo', while a layout with
7979+ // only one input and output channel would be called 'Mono'.
8080+ names: PortNames::const_default(),
8181+ }];
8282+8383+8484+ const MIDI_INPUT: MidiConfig = MidiConfig::None;
8585+ const MIDI_OUTPUT: MidiConfig = MidiConfig::None;
8686+8787+ const SAMPLE_ACCURATE_AUTOMATION: bool = true;
8888+8989+ // If the plugin can send or receive SysEx messages, it can define a type to wrap around those
9090+ // messages here. The type implements the `SysExMessage` trait, which allows conversion to and
9191+ // from plain byte buffers.
9292+ type SysExMessage = ();
9393+ // More advanced plugins can use this to run expensive background tasks. See the field's
9494+ // documentation for more information. `()` means that the plugin does not have any background
9595+ // tasks.
9696+ type BackgroundTask = ();
9797+9898+ fn params(&self) -> Arc<dyn Params> {
9999+ self.params.clone()
100100+ }
101101+102102+ fn initialize(
103103+ &mut self,
104104+ _audio_io_layout: &AudioIOLayout,
105105+ _buffer_config: &BufferConfig,
106106+ _context: &mut impl InitContext<Self>,
107107+ ) -> bool {
108108+ // Resize buffers and perform other potentially expensive initialization operations here.
109109+ // The `reset()` function is always called right after this function. You can remove this
110110+ // function if you do not need it.
111111+ true
112112+ }
113113+114114+ fn reset(&mut self) {
115115+ // Reset buffers and envelopes here. This can be called from the audio thread and may not
116116+ // allocate. You can remove this function if you do not need it.
117117+ }
118118+119119+ fn process(
120120+ &mut self,
121121+ buffer: &mut Buffer,
122122+ _aux: &mut AuxiliaryBuffers,
123123+ _context: &mut impl ProcessContext<Self>,
124124+ ) -> ProcessStatus {
125125+ for channel_samples in buffer.iter_samples() {
126126+ // Smoothing is optionally built into the parameters themselves
127127+ let gain = self.params.gain.smoothed.next();
128128+129129+ for sample in channel_samples {
130130+ *sample *= gain;
131131+ }
132132+ }
133133+134134+ ProcessStatus::Normal
135135+ }
136136+}
137137+138138+impl ClapPlugin for ShapemakerVST {
139139+ const CLAP_ID: &'static str = "works.ewen.shapemaker";
140140+ const CLAP_DESCRIPTION: Option<&'static str> = Some("A VST plugin for Shapemaker, an experimental audiovisual SVG-based rendering engine");
141141+ const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL);
142142+ const CLAP_SUPPORT_URL: Option<&'static str> = None;
143143+144144+ // Don't forget to change these features
145145+ const CLAP_FEATURES: &'static [ClapFeature] = &[ClapFeature::AudioEffect, ClapFeature::Stereo];
146146+}
147147+148148+impl Vst3Plugin for ShapemakerVST {
149149+ const VST3_CLASS_ID: [u8; 16] = *b"ewenlbhshapemake";
150150+151151+ // And also don't forget to change these categories
152152+ const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] =
153153+ &[Vst3SubCategory::Fx, Vst3SubCategory::Dynamics];
154154+}
155155+156156+nih_export_clap!(ShapemakerVST);
157157+nih_export_vst3!(ShapemakerVST);