1// All effects used by minimoog.dsp
2
3import("stdfaust.lib");
4
5process = _,_ : +
6	: component_echo
7	: component_flanger
8	: component_chorus
9	: component_freeverb;
10
11component_echo = environment {
12
13echo_group(x) = x; // Let layout2.dsp lay us out
14knobs_group(x) = ekg(x);
15switches_group(x)  = esg(x);
16
17dmax = 32768; // one and done
18dmaxs = float(dmax)/44100.0;
19
20Nnines = 1.8; // Increase until you get the desired maximum amount of smoothing when fbs==1
21//fastpow2 = ffunction(float fastpow2(float), "fast_pow2.h", "");
22fbspr(fbs) = 1.0 - pow(2.0, -3.33219*Nnines*fbs); // pole radius of feedback smoother
23inputSelect(gi) = _,0 : select2(gi);
24echo_mono(dmax,curdel,tapdel,fb,fbspr,gi) = inputSelect(gi) : (+:si.smooth(fbspr)
25<: de.fdelay(dmax,curdel),
26de.fdelay(dmax,tapdel))
27~(*(fb),!) : !,_;
28
29tau2pole(tau) = ba.if(tau>0, exp(-1.0/(tau*ma.SR)), 0.0);
30t60smoother(dEchoT60) = si.smooth(tau2pole(dEchoT60/6.91));
31
32dEchoT60 = knobs_group(vslider("[1] DelayT60 [midi:ctrl 60] [style:knob]", 0.5, 0, 100, 0.001));
33dEchoSamplesRaw = knobs_group(vslider("[0] Delay [midi:ctrl 61] [style:knob]", 0.5, 0.001, (dmaxs-0.001), 0.001)) * ma.SR;
34dEchoSamples = dEchoSamplesRaw : t60smoother(dEchoT60);
35warpRaw = knobs_group(vslider("[0] Warp [midi:ctrl 62] [style:knob]", 0, -1.0, 1.0, 0.001));
36
37scrubAmpRaw = 0;
38
39scrubPhaseRaw = 0;
40fb = knobs_group(vslider("[2] Feedback [midi:ctrl 2] [style:knob]", .3, 0.0, 1.0, 0.0001));
41amp = knobs_group(vslider("[3] Amp [midi:ctrl 75] [style:knob]", .5, 0, 1, 0.001)) : si.smooth(ba.tau2pole(ampT60/6.91));
42
43ampT60 = 0.15661;
44fbs = knobs_group(vslider("[5] [midi:ctrl 76] FeedbackSm [style:knob]", 0, 0, 1, 0.00001));
45gi = switches_group(1-vslider("[7] [midi:ctrl 105] EnableEcho[style:knob]",0,0,1,1)); // "ground input" switches input to zeros
46
47// Warp and Scrubber stuff:
48enableEcho = (scrubAmpRaw > 0.00001);
49triggerScrubOn = (enableEcho - enableEcho') > 0;  // enableEcho went 0 to 1
50triggerScrubOff = (enableEcho - enableEcho') < 0; // enableEcho went 1 to 0
51// Ramps up only during scrub "hold" time and is otherwise zero:
52counter = (enableEcho * (triggerScrubOn : + ~ +(1) * enableEcho : -(2))) & (dmax-1);
53// implementation that continues scrubbing where it left off:
54
55scrubPhase = scrubPhaseRaw : t60smoother(dEchoT60*(1-triggerScrubOff));
56scrubAmp = scrubAmpRaw : t60smoother(dEchoT60*(1-triggerScrubOff));
57warp = warpRaw : t60smoother(dEchoT60);
58dTapSamplesRaw = dEchoSamplesRaw * (1.0 + warp + scrubPhase * scrubAmp) + float(counter);
59dTapSamples = dTapSamplesRaw : t60smoother(dEchoT60*(1-triggerScrubOff));
60
61echo_process = _ <: _, amp * echo_mono(dmax,dEchoSamples,dTapSamples,fb,fbspr(fbs),gi) : +;
62
63}.echo_process;
64
65component_flanger = environment {
66
67// Created from flange.dsp 2015/06/21
68
69flanger_mono(dmax,curdel,depth,fb,invert,lfoshape)
70	= _ <: _, (-:de.fdelay(dmax,curdel)) ~ *(fb) : _, *(select2(invert,depth,0-depth)) : + : *(1/(1+depth)); // ideal for dc and reinforced sinusoids (in-phase summed signals)
71
72flanger_process = ba.bypass1(fbp,flanger_mono_gui);
73
74// Kill the groups to save vertical space:
75meter_group(x) = flsg(x);
76ctl_group(x) = flkg(x);
77del_group(x) = flkg(x);
78lvl_group(x) = flkf(x);
79
80flangeview = lfo(freq);
81
82flanger_mono_gui = attach(flangeview) : flanger_mono(dmax,curdel,depth,fb,invert,lfoshape);
83
84sinlfo(freq) = (1 + os.oscrs(freq))/2;
85trilfo(freq) = 1.0-abs(os.saw1(freq));
86lfo(f) = (lfoshape * trilfo(f)) + ((1-lfoshape) * sinlfo(f));
87
88dmax = 2048;
89odflange = 44; // ~1 ms at 44.1 kHz = min delay
90dflange  = ((dmax-1)-odflange)*del_group(vslider("[1] Delay [midi:ctrl 50][style:knob]", 0.22, 0, 1, 1));
91freq     = ctl_group(vslider("[1] Rate [midi:ctrl 51] [unit:Hz] [style:knob]", 0.5, 0, 10, 0.01)) : si.smooth(ba.tau2pole(freqT60/6.91));
92
93freqT60  = 0.15661;
94depth    = ctl_group(vslider("[3] Depth [midi:ctrl 52] [style:knob]", .75, 0, 1, 0.001)) : si.smooth(ba.tau2pole(depthT60/6.91));
95
96depthT60 = 0.15661;
97fb       = ctl_group(vslider("[5] Feedback [midi:ctrl 53] [style:knob]", 0, -0.995, 0.99, 0.001)) : si.smooth(ba.tau2pole(fbT60/6.91));
98
99fbT60    = 0.15661;
100lfoshape = ctl_group(vslider("[7] Waveshape [midi:ctrl 54] [style:knob]", 0, 0, 1, 0.001));
101curdel   = odflange+dflange*lfo(freq);
102
103fbp = 1-int(flsg(vslider("[0] Enable [midi:ctrl 102][style:knob]",0,0,1,1)));
104
105invert = flsg(vslider("[1] Invert [midi:ctrl 49][style:knob]",0,0,1,1):int);
106
107}.flanger_process;
108
109component_chorus = environment {
110
111voices = 8; // MUST BE EVEN
112chorus_process = ba.bypass1to2(cbp,chorus_mono(dmax,curdel,rate,sigma,do2,voices));
113
114dmax = 8192;
115curdel = dmax * ckg(vslider("[0] Delay [midi:ctrl 55] [style:knob]", 0.5, 0, 1, 1)) : si.smooth(0.999);
116rateMax = 7.0; // Hz
117rateMin = 0.01;
118rateT60 = 0.15661;
119rate = ckg(vslider("[1] Rate [midi:ctrl 56] [unit:Hz] [style:knob]", 0.5, rateMin, rateMax, 0.0001))
120       : si.smooth(ba.tau2pole(rateT60/6.91));
121
122depth = ckg(vslider("[4] Depth [midi:ctrl 57] [style:knob]", 0.5, 0, 1, 0.001)) : si.smooth(ba.tau2pole(depthT60/6.91));
123
124depthT60 = 0.15661;
125delayPerVoice = 0.5*curdel/voices;
126sigma = delayPerVoice * ckg(vslider("[6] Deviation [midi:ctrl 58] [style:knob]",0.5,0,1,0.001)) : si.smooth(0.999);
127
128periodic = 1;
129
130do2 = depth;   // use when depth=1 means "multivibrato" effect (no original => all are modulated)
131cbp = 1-int(csg(vslider("[0] Enable [midi:ctrl 103][style:knob]",0,0,1,1)));
132
133chorus_mono(dmax,curdel,rate,sigma,do2,voices)
134    = _ <: (*(1-do2)<:_,_),(*(do2) <: par(i,voices,voice(i)) :> _,_) : ro.interleave(2,2) : +,+
135    with {
136        angle(i) = 2*ma.PI*(i/2)/voices + (i%2)*ma.PI/2;
137        voice(i) = de.fdelay(dmax,min(dmax,del(i))) * cos(angle(i));
138        del(i) = curdel*(i+1)/voices + dev(i);
139        rates(i) = rate/float(i+1);
140        dev(i) = sigma * os.oscp(rates(i),i*2*ma.PI/voices);
141    };
142
143}.chorus_process;
144
145component_freeverb = environment {
146
147import("stdfaust.lib");
148
149declare name        "freeverb";
150declare version     "1.0";
151declare author      "Grame";
152declare license     "BSD";
153declare copyright   "(c) GRAME 2006 and MoForte Inc. 2017";
154declare reference   "https://ccrma.stanford.edu/~jos/pasp/Freeverb.html";
155
156//======================================================
157//
158//                      Freeverb
159//        Faster version using fixed delays (20% gain)
160//
161//======================================================
162
163// Constant Parameters
164//--------------------
165
166fixedgain   = 0.015; //value of the gain of fxctrl
167scalewet    = 3.0;
168scaledry    = 2.0;
169scaledamp   = 0.4;
170scaleroom   = 0.28;
171offsetroom  = 0.7;
172initialroom = 0.5;
173initialdamp = 0.5;
174initialwet  = 1.0/scalewet;
175initialdry  = 0;
176initialwidth= 1.0;
177initialmode = 0.0;
178freezemode  = 0.5;
179stereospread= 23;
180allpassfeed = 0.5; //feedback of the delays used in allpass filters
181
182// Filter Parameters
183//------------------
184
185combtuningL1    = 1116;
186combtuningL2    = 1188;
187combtuningL3    = 1277;
188combtuningL4    = 1356;
189combtuningL5    = 1422;
190combtuningL6    = 1491;
191combtuningL7    = 1557;
192combtuningL8    = 1617;
193
194allpasstuningL1 = 556;
195allpasstuningL2 = 441;
196allpasstuningL3 = 341;
197allpasstuningL4 = 225;
198
199// Control Sliders
200//--------------------
201// Damp : filters the high frequencies of the echoes (especially active for great values of RoomSize)
202// RoomSize : size of the reverberation room
203// Dry : original signal
204// Wet : reverberated signal
205
206dampSlider      = rkg(vslider("Damp [midi:ctrl 3] [style:knob]",0.5, 0, 1, 0.025))*scaledamp;
207roomsizeSlider  = rkg(vslider("RoomSize [midi:ctrl 4] [style:knob]", 0.5, 0, 1, 0.025))*scaleroom + offsetroom;
208wetSlider       = rkg(vslider("Wet [midi:ctrl 79] [style:knob]", 0.3333, 0, 1, 0.025));
209combfeed        = roomsizeSlider;
210
211// Comb and Allpass filters
212//-------------------------
213
214allpass(dt,fb) = (_,_ <: (*(fb),_:+:@(dt)), -) ~ _ : (!,_);
215
216comb(dt, fb, damp) = (+:@(dt)) ~ (*(1-damp) : (+ ~ *(damp)) : *(fb));
217
218// Reverb components
219//------------------
220
221monoReverb(fb1, fb2, damp, spread)
222= _ <:  comb(combtuningL1+spread, fb1, damp),
223comb(combtuningL2+spread, fb1, damp),
224comb(combtuningL3+spread, fb1, damp),
225comb(combtuningL4+spread, fb1, damp),
226comb(combtuningL5+spread, fb1, damp),
227comb(combtuningL6+spread, fb1, damp),
228comb(combtuningL7+spread, fb1, damp),
229comb(combtuningL8+spread, fb1, damp)
230+>
231allpass (allpasstuningL1+spread, fb2)
232:   allpass (allpasstuningL2+spread, fb2)
233:   allpass (allpasstuningL3+spread, fb2)
234:   allpass (allpasstuningL4+spread, fb2)
235;
236
237monoReverbToStereo(fb1, fb2, damp, spread)
238= + <: monoReverb(fb1, fb2, damp, 0) <: _,_;
239stereoReverb(fb1, fb2, damp, spread)
240= + <:  monoReverb(fb1, fb2, damp, 0), monoReverb(fb1, fb2, damp, spread);
241monoToStereoReverb(fb1, fb2, damp, spread)
242= _ <: monoReverb(fb1, fb2, damp, 0), monoReverb(fb1, fb2, damp, spread);
243
244// fxctrl : add an input gain and a wet-dry control to a stereo FX
245//----------------------------------------------------------------
246
247fxctrl(g,w,Fx) =  _,_ <: (*(g),*(g) : Fx : *(w),*(w)), *(1-w), *(1-w) +> _,_;
248
249rbp = 1-int(rsg(vslider("[0] Enable [midi:ctrl 104][style:knob]",0,0,1,1)));
250
251// Freeverb
252//---------
253
254//JOS:freeverb = fxctrl(fixedgain, wetSlider, stereoReverb(combfeed, allpassfeed, dampSlider, stereospread));
255freeverb = fxctrl(fixedgain, wetSlider, monoReverbToStereo(combfeed, allpassfeed, dampSlider, stereospread));
256
257freeverb_process = ba.bypass2(rbp,freeverb);
258
259}.freeverb_process;
260
261// This layout loosely follows the MiniMoog-V
262// Arturia-only features are labeled
263// Original versions also added where different
264
265// Need vrocker and hrocker toggle switches in Faust!
266// Need orange and blue color choices
267//   Orange => Connect modulation sources to their destinations
268//    Blue  => Turn audio sources On and Off
269// - and later -
270//   White  => Turn performance features On and Off
271//   Black  => Select between modulation sources
272//   Julius Smith for Analog Devices 3/1/2017
273
274vrocker(x) = checkbox("%%x [style:vrocker]");
275hrocker(x) = checkbox("%%x [style:hrocker]");
276vrockerblue(x) = checkbox("%x  [style:vrocker] [color:blue]");
277vrockerblue(x) = checkbox("%x [style:vrocker] [color:blue]");
278 // USAGE: vrockerorange("[0] ModulationEnable");
279
280hrockerblue(x) = checkbox("%%x [style:hrocker] [color:blue]");
281vrockerred(x) = checkbox("%%x [style:vrocker] [color:red]");
282hrockerred(x) = checkbox("%%x [style:hrocker] [color:red]");
283
284declare designer "Robert A. Moog";
285
286mmg(x) = hgroup("",x); // Minimoog + Effects
287  synthg(x) = mmg(vgroup("[0] Minimoog",x));
288  fxg(x) = mmg(hgroup("[1] Effects",x));
289  mg(x) = synthg(hgroup("[0]",x));
290    cg(x) = mg(vgroup("[0] Controllers",x)); // Formerly named "Modules" but "Minimoog" group-title is enough
291      vg(x) = cg(hgroup("[0] Master Volume", x));
292      dg(x) = cg(hgroup("[1] Oscillator Tuning & Switching", x));
293        // Tune knob = master tune
294        dsg(x) = dg(vgroup("[1] Switches", x));
295	  // Oscillator Modulation HrockerRed => apply Modulation Mix output to osc1&2 pitches
296	  // [MOVED here from osc3 group] Osc 3 Control VrockerRed => use osc3 as LFO instead of osc3
297      gmmg(x) = cg(hgroup("[2] Glide and ModMix", x));
298        // Glide knob [0:10] = portamento speed
299        // Modulation Mix knob [0:10] (between Osc3 and Noise) = mix of noise and osc3 modulating osc1&2 pitch and/or VCF freq
300    og(x) = mg(vgroup("[1] Oscillator Bank", x));
301      osc1(x) = og(hgroup("[1] Oscillator 1", x));
302        // UNUSED Control switch (for alignment) - Could put Oscillator Modulation switch there
303        // Range rotary switch: LO (slow pulses or rhythm), 32', 16', 8', 4', 2'
304        // Frequency <something> switch: LED to right
305        // Waveform rotary switch: tri, impulse/bent-triangle, saw, pulseWide, pulseMed, pulseNarrow
306      osc2(x) = og(hgroup("[2] Oscillator 2", x));
307        // UNUSED (originall) or Osc 2 Control VrockerRed
308        // Range rotary switch: LO, 32', 16', 8', 4', 2'
309        // Detuning knob: -7 to 7 [NO SWITCH]
310        // Waveform rotary switch: tri, impulse(?), saw, pulseWide, pulseMed, pulseNarrow
311      osc3(x) = og(hgroup("[3] Oscillator 3", x));
312        // Osc 3 Control VrockerRed => use osc3 as LFO instead of osc3
313        // Range rotary switch: LO, 32', 16', 8', 4', 2'
314        // Detuning knob: -7 to 7 [NO SWITCH]
315        // Waveform rotary switch: tri, impulse(?), saw, pulseWide, pulseMed, pulseNarrow
316    mixg(x) = mg(vgroup("[2] Mixer", x));
317      // Each row 5 slots to maintain alignment and include red rockers joining VCF area:
318      mr1(x) = mixg(hgroup("[0] Osc1", x)); // mixer row 1 =
319      // Osc1 Volume and Osc1 HrockerBlue & _ & _ & Filter Modulation HrockerRed
320      // Filter Modulation => Modulation Mix output to VCF freq
321      mr2(x) = mixg(hgroup("[1] Ext In, KeyCtl", x)); // row 2 = Ext In HrockerBlue and Vol and Overload LED and Keyboard Ctl HrockerRed 1
322      mr3(x) = mixg(hgroup("[2] Osc2", x)); // = Osc2 Volume and Osc2 HrockerBlue and Keyboard Ctl HrockerRed 2
323      // Keyboard Control Modulation 1&2 => 0, 1/3, 2/3, all of Keyboard Control Signal ("gate?") applied to VCF freq
324      mr4(x) = mixg(hgroup("[3] Noise", x)); // = Noise HrockerBlue and Volume and Noise Type VrockerBlue
325        mr4cbg(x) = mr4(vgroup("[1]", x)); // = Noise Off and White/Pink selection
326	// two rockers
327      mr5(x) = mixg(hgroup("[4] Osc3", x)); //  Osc3 Volume and Osc3 HrockerBlue
328    modg(x) = mg(vgroup("[3] Modifiers", x));
329      vcfg(x) = modg(vgroup("[0] Filter", x));
330        vcf1(x) = vcfg(hgroup("[0] [tooltip:freq, Q, ContourScale]", x));
331	  vcf1cbg(x) = vcf1(vgroup("[0] [tooltip:two checkboxes]", x));
332          // Filter Modulation switch
333          // VCF Off switch
334        // Corner Frequency knob
335        // Filter Emphasis knob
336        // Amount of Contour knob
337        vcf2(x) = vcfg(hgroup("[1] Filter Contour [tooltip:AttFilt, DecFilt, Sustain Level for Filter Contour]", x));
338        // Attack Time knob
339        // Decay Time knob
340        // Sustain Level knob
341      ng(x) = modg(hgroup("[1] Loudness Contour", x));
342        // Attack Time knob
343        // Decay Time knob
344        // Sustain Level knob
345    echog(x) = fxg(hgroup("[4] Echo",x));
346      ekg(x) = echog(vgroup("[0] Knobs",x));
347      esg(x) = echog(vgroup("[1] Switches",x));
348    flg(x) = fxg(hgroup("[5] Flanger",x));
349      flkg(x) = flg(vgroup("[0] Knobs",x));
350      flsg(x) = flg(vgroup("[1] Switches",x));
351    chg(x) = fxg(hgroup("[6] Chorus",x));
352      ckg(x) = chg(vgroup("[0] Knobs",x));
353      csg(x) = chg(vgroup("[1] Switches",x));
354    rg(x) = fxg(hgroup("[7] Reverb",x));
355      rkg(x) = rg(vgroup("[0] Knobs",x));
356      rsg(x) = rg(vgroup("[1] Switches",x));
357    outg(x) = fxg(vgroup("[8] Output", x));
358      volg(x) = outg(hgroup("[0] Volume Main Output", x));
359        // Volume knob [0-10]
360	// Unison switch (Arturia) or Output connect/disconnect switch (original)
361	//   When set, all voices are stacked and instrument is in mono mode
362      tunerg(x) = outg(hgroup("[1] A-440 Switch", x));
363      vdtpolyg(x) = outg(hgroup("[2] Voice Detune / Poly", x));
364        // Voice Detune knob [0-10] (Arturia) or
365	// Polyphonic switch [red LED below] (Arturia)
366	//   When set, instrument is in polyphonic mode with one oscillator per key
367    clipg(x) = fxg(vgroup("[9] Soft Clip", x));
368	// Soft Clipping switch [red LED above]
369  kg(x) = synthg(hgroup("[1] Keyboard Group", x)); // Keyboard was 3 1/2 octaves
370    ws(x) = kg(vgroup("[0] Wheels and Switches", x));
371      s1g(x) = ws(hgroup("[0] Jacks and Rockers", x));
372        jg(x) = s1g(vgroup("[0] MiniJacks",x));
373        gdlg(x) = s1g(vgroup("[1] Glide/Decay/Legato Enables",x)); // Arturia
374	// Glide Hrocker (see original Button version below)
375	// Decay Hrocker (see original Button version below) => Sets Release (R) of ADSR to either 0 or Decay (R)
376	// Legato Hrocker (not in original)
377      s2g(x) = ws(hgroup("[1] [tooltip:Wheels+]", x));
378        bg(x) = s2g(vgroup("[0] [tooltip:Bend Enable and Range]", x));
379        wg(x) = s2g(hgroup("[1] [tooltip:Bend and Mod Wheels]", x));
380	// Using Glide/Decay/Legato enables above following Arturia:
381	//   dg(x) = s2g(hgroup("[2] Glide and Decay momentary pushbuttons", x));
382	//   Glide Button injects portamento as set by Glide knob
383	//   Decay Button uses decay of Loudness Contour (else 0)
384    keys(x) = kg(hgroup("[1] [tooltip:Keys]", x));
385      gg(x) = keys(hgroup("[0] [tooltip: Gates]",x));
386      // leave slot 1 open for sustain (below)
387