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