1+ #include " SC_PlugIn.h"
2+
3+ static InterfaceTable *ft;
4+
5+ struct AnalogEcho : public Unit {
6+ // Max delay in seconds.
7+ float maxdelay;
8+
9+ // Size of the buffer in samples, always a power of 2
10+ int bufsize;
11+ // bufsize - 1, so the modulo function can be replaced with a faster bitwise and
12+ int mask;
13+ // The buffer itself. This is an internal buffer, and is not connected with any Buffer instance.
14+ // It must be allocated in Ctor and freed in Dtor.
15+ float * buf;
16+
17+ // Position of the write head.
18+ int writephase;
19+
20+ // State of the one-pole lowpass filter.
21+ float s1;
22+ };
23+
24+
25+ static void AnalogEcho_next (AnalogEcho *unit, int inNumSamples);
26+ static void AnalogEcho_Ctor (AnalogEcho* unit);
27+ static void AnalogEcho_Dtor (AnalogEcho* unit);
28+
29+
30+ void AnalogEcho_Ctor (AnalogEcho* unit) {
31+ SETCALC (AnalogEcho_next);
32+
33+ unit->maxdelay = IN0 (2 );
34+
35+ // To get the buffer size in samples, take the sample rate times the length in seconds.
36+ // The buffer size doesn't NEED to be a power of two, but if you're doing a lot of moduloing then it's faster that way.
37+ unit->bufsize = NEXTPOWEROFTWO ((float )SAMPLERATE * unit->maxdelay );
38+ unit->mask = unit->bufsize - 1 ;
39+
40+ unit->writephase = 0 ;
41+ unit->s1 = 0 ;
42+
43+ // Allocate the buffer. Do NOT use malloc!
44+ // SuperCollider provides special real-time-safe allocation and freeing functions.
45+ unit->buf = (float *)RTAlloc (unit->mWorld , unit->bufsize * sizeof (float ));
46+ if (unit->buf == NULL ) {
47+ SETCALC (ft->fClearUnitOutputs );
48+ ClearUnitOutputs (unit, 1 );
49+
50+ if (unit->mWorld ->mVerbosity > -2 ) {
51+ Print (" Failed to allocate memory for AnalogEcho ugen.\n " );
52+ }
53+ }
54+ // Fill the buffer with zeros.
55+ memset (unit->buf , 0 , unit->bufsize * sizeof (float ));
56+
57+ AnalogEcho_next (unit, 1 );
58+ }
59+
60+ // this must be named PluginName_Dtor.
61+ void AnalogEcho_Dtor (AnalogEcho* unit) {
62+ // Free the memory.
63+ RTFree (unit->mWorld , unit->buf );
64+ }
65+
66+ void AnalogEcho_next (AnalogEcho *unit, int inNumSamples)
67+ {
68+ // audio-rate input signal
69+ float *in = IN (0 );
70+ // audio-rate output signal
71+ float *out = OUT (0 );
72+ // control-rate delay
73+ float delay = IN0 (1 );
74+ // control-rate feedback coefficient
75+ float fb = IN0 (3 );
76+ // control-rate filter coefficient
77+ float coeff = IN0 (4 );
78+
79+ float * buf = unit->buf ;
80+ int mask = unit->mask ;
81+ int writephase = unit->writephase ;
82+ float s1 = unit->s1 ;
83+
84+ // Cap the delay at maxdelay
85+ if (delay > unit->maxdelay ) {
86+ delay = unit->maxdelay ;
87+ }
88+
89+ // Compute the delay in samples and the integer and fractional parts of this delay.
90+ float delay_samples = (float )SAMPLERATE * unit->maxdelay ;
91+ int offset = delay_samples;
92+ float frac = delay_samples - offset;
93+
94+ // Precompute a filter coefficient.
95+ float a = 1 - std::abs (coeff);
96+
97+ for (int i = 0 ; i < inNumSamples; i++) {
98+
99+ // Four integer phases into the buffer
100+ int phase1 = writephase - offset;
101+ int phase2 = phase1 - 1 ;
102+ int phase3 = phase1 - 2 ;
103+ int phase0 = phase1 + 1 ;
104+ float d0 = buf[phase0 & mask];
105+ float d1 = buf[phase1 & mask];
106+ float d2 = buf[phase2 & mask];
107+ float d3 = buf[phase3 & mask];
108+ // Use cubic interpolation with the fractional part of the delay in samples
109+ float delayed = cubicinterp (frac, d0, d1, d2, d3);
110+
111+ // Apply lowpass filter and store the state of the filter.
112+ float lowpassed = a * delayed + coeff * s1;
113+ s1 = lowpassed;
114+
115+ // Multiply by feedback coefficient and add to input signal.
116+ // zapgremlins gets rid of Bad Things like denormals, explosions, etc.
117+ out[i] = zapgremlins (in[i] + fb * lowpassed);
118+ buf[writephase] = out[i];
119+
120+ // Here's why the buffer size is a power of two -- otherwise this becomes a much more
121+ // expensive modulo operation.
122+ writephase = (writephase + 1 ) & mask;
123+ }
124+
125+ // These two variables were updated and need to be stored back into the state of the UGen.
126+ unit->writephase = writephase;
127+ unit->s1 = s1;
128+ }
129+
130+
131+ PluginLoad (AnalogEcho)
132+ {
133+ ft = inTable;
134+ // ATTENTION! This has changed!
135+ // In the previous examples this was DefineSimpleUnit.
136+ DefineDtorUnit (AnalogEcho);
137+ }
0 commit comments