~kdsch/drumvoice

2cf9909e54aa9106369a341769c9c94ae332f859 — Karl Schultheisz 2 years ago fa24888
messy commit
4 files changed, 305 insertions(+), 80 deletions(-)

A Makefile
A README.md
A TODO
M drum-voice.dsp
A Makefile => Makefile +13 -0
@@ 0,0 1,13 @@
INSTALL=/usr/local/lib/lv2

drumvoice.lv2: drumvoice.dsp
	faust2lv2 drumvoice.dsp

install: uninstall drumvoice.lv2
	cp -r drumvoice.lv2 $(INSTALL)

uninstall:
	rm -rf $(INSTALL)/drumvoice.lv2

clean:
	rm -rf drumvoice.lv2

A README.md => README.md +37 -0
@@ 0,0 1,37 @@
Drumvoice is a percussion instrument LV2 plugin.

I made it because I was having fun creating synth percussion in
Audacity one day but ran into the problem of not being able
to make ringy snare drums. True, a lot of electronic music
doesn't really use that type of sound, with the main exceptions
being drum & bass and big beat, both of which get it typically
by sampling funk breakbeats, not by synthesizing their own
snares. But like I said, I was having fun doing that and wanted
a ringy snare. So I figured out how to make an FM synth give me
the ringing component and then blended that in with a typical
noise-and-sine synth snare, and it sounded awesome.

Then I started playing with changing the balance between snare
and ring with velocity, and I realized that ghost notes ring
a little more than ordinary notes.

So when I created drumvoice, I did it to at least be able to
make ringy snares that get ringier as they get quieter, just
like a real snare drum. But it still sounds quite a lot like
a synth snare, and that's OK.

Drumvoice can also make lots of other percussion sounds, like
kick drums, toms, single hand claps, and all kinds of smaps
and blinks. Part of the reason it can do this is because it has
a sine wave oscillator with a really fast pitch envelope. So
fast that it makes more of a snapping sound than a chirping
sound, which is good news, because not many synths pay attention
to what happens when their envelopes are set really fast.
Drumvoice gives you a logarithmically-scaled control over the
envelope decay time, which makes tuning your attacks quite fun.

Drumvoice has a bandpass-filtered noise source. You can use
it to make snare and cymbal sounds, or to make fake reverb on
other sounds by tuning it to the right bandwidth and frequency.
A little rumble with your boom is a good idea.


A TODO => TODO +12 -0
@@ 0,0 1,12 @@
noise source

- self-oscillating nonlinear BPF; expected to give
  a more predictable output level.

- AD envelope. Allows faking a reverb with predelay;
  lets us put the filter before the amplifier
  and still avoid unwanted click attacks
  by using nonzero attack time
  (this also gives better control
  over decay; no ringing tail at high Q) 


M drum-voice.dsp => drum-voice.dsp +243 -80
@@ 5,60 5,46 @@ declare license   "MIT";
declare copyright "Karl Schultheisz 2020";

// https://fausteditor.grame.fr/
// https://faustide.grame.fr/
// https://faust.grame.fr/doc/manual/index.html
// https://faust.grame.fr/doc/libraries/index.html
// https://manual.ardour.org/working-with-plugins/getting-plugins/
// https://bitbucket.org/agraef/faust-lv2/src/master/faust2lv2

// A percussion synthesizer using three components:
// 
// - a sine oscillator
// - a bandpass-filtered noise source
// - a single-operator FM oscillator
// 
// Each component has a dedicated exponentially-decaying amplitude envelope.
//
// It is capable of classic synth drum sounds as well as more realistic
// drums due to the FM voice, which can be used to generate complex, inharmonic resonances.
//
// The sine and FM components are phase-synchronized with the trigger signal,
// so that they always begin a note at phase zero.
//
// The noise source's bandpass filter is gain-adjusted as the Q varies to maintain
// a consistent loudness.
// A percussion instrument.

import ("stdfaust.lib");

// We are not polyphonic!
// declare options "[nvoices:1]";
//declare nvoices "16";

// For MIDI interface
/*
freq
	= nentry ("freq", 440, 20, 7040, 1)
	;
// For LV2 MIDI instrument interface
declare nvoices "4";
freq = nentry ("freq[hidden:xx]", 440, 20, 7040, 1   ); // not used
gain = nentry ("gain[hidden:xx]",   1,  0,   10, 0.01);
gate = button ("gate[hidden:xx]");

gain
	= nentry ("gain", 1, 0, 10, 0.01)
	;

gate
	= button ("gate")
// velocity_sensitive implements velocity sensitivity.
// At maximum sensitivity and maximum velocity, the value is two.
velocity_sensitive (sensitivity, velocity)
	= 1 + sensitivity * (2 * velocity - 1)
	;
*/


// level is a volume control knob.
level (default)
	= hslider ("level[style:knob][unit:dB]", default, -96, 0, 1)
	= vslider
		( "level[unit:dB]"
		, default
		, -96
		, 0
		, 1
		)
	: ba.db2linear
	// : si.smoo
	;


// envelope provides an exponentially-decaying envelope.
envelope (gate, decay)
// d_envelope provides an exponentially-decaying envelope.
d_envelope (decay, gate)
	= gate
	: ba.impulsify
	: an.amp_follower (decay)


@@ 84,27 70,68 @@ sinesync (gate, freq)
	;


zerolog (c, x)
	= x * c ^ (1 - x)
	;


// sine_part provides a sinusoidal tone component.
sine_part (gate)
sine_part (gate, vel)
	= level (-12)
	* sinesync (gate, freq)
	* envelope (gate, amp_decay)
	* d_envelope (amp_decay, gate)
	* velocity_sensitive (sensitivity, vel)
	with
		{ amp_decay
				= 0.1 * 2 ^ vslider ("[3]amp decay[style:knob]", -1.5, -7, 5, 0.1)
				= 0.1 * 2
				^ vslider
					( "[3]sine amp decay"
					, -1.5
					, -7
					, 5
					, 0.01
					)
			  ;

			sensitivity
				= vslider
					( "[4]sine amplitude velocity sensitivity"
					, 1
					, -1
					, 1
					, 0.01
					)
				;

			freq_decay
		  	= 0.01 * 2 ^ vslider ("[2]pitch decay[style:knob]", -1.5, -6, 6, 0.1)
		  	= 0.01 * 2
		  	^ vslider
		  		( "[2]sine chirp decay"
		  		, -1.5
		  		, -6
		  		, 6
		  		, 0.01
		  		)
				;

			intensity
				= vslider ("[1]pitch intensity[style:knob][unit:oct]", 1.5, 0, 4, 0.1)
				= vslider
					( "[1]sine chirp intensity[unit:oct]"
					, 1.5
					, 0
					, 4
					, 0.01
					)
				;

			octave
				= vslider ("[0]pitch[style:knob][unit:oct]", 0, -3, 6, 0.1)
				// : si.smoo
				= vslider
					( "[0]sine pitch[unit:oct]"
					, 0
					, -3
					, 6
					, 0.01
					)
				;

			freq


@@ 112,34 139,71 @@ sine_part (gate)
				;

			octave_env
				= intensity * envelope (gate, freq_decay)
				= intensity * d_envelope (freq_decay, gate)
				;
		}
	;


// noise_part provides a bandlimited noise component.
noise_part (gate)
noise_part (gate, vel)
	= level (-6)
	* no.pink_noise
	// Note how the filter comes after the envelope.
	// Had we done the reverse,
	// the envelope would introduce
	// a click upon attack.
	* envelope (gate, amp_decay)
	: fi.resonbp (freq, q, 4 * q ^ -0.5)
	* ad_envelope (amp_attack, amp_decay, gate)
	* velocity_sensitive (sensitivity, vel)
	with
		{ amp_decay
				= 0.1 * 2 ^ vslider ("[3]amp decay[style:knob]", -1.4, -7, 5, 0.1)
				= 0.1 * 2
				^ vslider
					( "[3]noise amp decay"
					, -1.4
					, -7
					, 5
					, 0.1
					)
				;

			amp_attack
				= 0.1 * 2
				^ vslider
					( "[3]noise amp attack"
					, -1.4
					, -7
					, 5
					, 0.1
					)
				;

			sensitivity
				= vslider
					("[4]noise amplitude velocity sensitivity"
					, 1
					, -1
					, 1
					, 0.01
					)
				;

			q
				= 2 ^ vslider ("[1]resonance[style:knob][scale:log]", -0.8, -3, 6, 0.1)
				= 2
				^ vslider
					( "[1]noise resonance"
					, -0.8
					, -3
					, 6
					, 0.1
					)
				;

			octave
				= vslider ("[0]frequency[style:knob][unit:oct]", 4.5, -3, 6, 0.1)
				// : si.smoo
				= vslider
					( "[0]noise frequency[unit:oct]"
					, 4.5
					, -3
					, 6
					, 0.1
					)
				;

			freq


@@ 149,57 213,113 @@ noise_part (gate)
	;


// fm_sync is a single-operator FM synthesizer with trigger hardsyncing.
// ad_envelope provides an envelope with a linear
// attack followed immediately by an exponential decay.
ad_envelope (attack_time, decay_time, gate)
	= attack * attack_phase
	: an.amp_follower (decay_time)
	with
		{
			linear_attack (sec, trig)
				= ba.countup (samp, trig) / samp
				with
					{ samp
						= ba.sec2samp (sec)
						;
					}
				;

			attack
				= gate
				: ba.impulsify
				: linear_attack (attack_time)
				;
			
			attack_phase
				= attack < 1
				;
		}
	;


// fm_sync is a single-operator FM synthesizer
// with trigger hardsyncing.
fm_sync (modf, carf, intensity, gate)
	= sinesync (gate, carf * (1 + intensity * sinesync (gate, modf)))
	= carf * (modf : sinesync (gate) * intensity + 1)
	: sinesync (gate)
	;


// fm_part provides the FM tone component.
fm_part (gate)
fm_part (gate, vel)
	= level (-44)
	* fm_sync (modulator, carrier, intensity, gate)
	* envelope (gate, amp_decay)
	* d_envelope (amp_decay, gate)
	* velocity_sensitive (sensitivity, vel)
	with
		{ amp_decay
				= 0.1 * 2 ^ vslider ("[3]amp decay[style:knob]", -0.3, -7, 5, 0.1)
				= 0.1 * 2
				^ vslider
					( "[3]fm amp decay"
					, -0.3
					, -7
					, 5
					, 0.01
					)
				;

			freqs
				= (modulator, carrier)
			sensitivity
				= vslider
					( "[4]fm amplitude velocity sensitivity"
					, 1
					, -1
					, 1
					, 0.01
					)
				;

			modulator
				= 217 * 2 ^ vslider ("[0]modulator[style:knob][scale:log]", 0, -3, 6, 0.1)
				= 217 * 2
				^ vslider
					( "[0]fm modulator"
					, 0
					, -3
					, 6
					, 0.01
					)
				;

			carrier
				= 988 * 2 ^ vslider ("[1]carrier[style:knob][scale:log]", 0, -4, 5, 0.1)
				= 988 * 2
				^ vslider
					( "[1]fm carrier"
					, 0
					, -4
					, 5
					, 0.01
					)
				;

			intensity
				= vslider ("[2]index[style:knob]", 0.5, 0, 2, 0.01)
				;

			indices
				= (intensity * carrier)
				= vslider
					( "[2]fm index"
					, 0.5
					, 0
					, 2
					, 0.01
					)
				;
		}
	;


// output is the sum of the three components,
// voice is the sum of the three components,
// each with a separate UI group.
output
	= hgroup ("sine", sine_part) (gate)
	+ hgroup ("noise", noise_part) (gate)
	+ hgroup ("fm", fm_part) (gate)
	with 
		{ gate
				// = button("trig")
  			= os.lf_pulsetrainpos (2, 0.5)
  			;
		}
voice (freq, vel, gate)
	= hgroup ("sine", sine_part) (gate, vel)
	+ hgroup ("noise", noise_part) (gate, vel)
	+ hgroup ("fm", fm_part) (gate, vel)
	: strict (freq)
	;




@@ 209,10 329,53 @@ strict (a, b)
	;


freq_scale (x)
	= 20 * 2 ^ x
	;


time_scale (x)
	= 20 * 2 ^ x
	;


sine_part2 (octave, intensity, freq_decay, amp_decay, gate)
	= freq_scale (octave + intensity * d_envelope (freq_decay, gate))
	: sinesync (gate)
	* d_envelope (amp_decay, gate)
	;


// We'll deal with level and velocity sensitivity elsewhere
noise_part2 (octave, q, attack, decay, gate)
	= no.pink_noise
	: fi.resonbp (freq_scale (octave), q, 8 * q ^ -0.5)
	* ad_envelope (attack, decay, gate)
	;


fm_part2 (octave, ratio, index, decay, gate)
	= fm_sync (carrier * ratio, carrier, index, gate)
	* d_envelope (decay, gate)
	with
		{ carrier
				= freq_scale (octave)
				;
		}
	;


process
	= vgroup ("drum voice", output)
	// : strict (freq)
	// : strict (gain)
	= hgroup
		( "drum voice"
		, vgroup
			( "MIDI"
			, gain
			, freq
			, gate
			)
		: voice
		)
	<: _, _
	;