1 /**
2 * Copyright 2017 Cut Through Recordings
3 * License: MIT License
4 * Author(s): Ethan Reker
5 */
6 module ddsp.effect.digitaldelay;
7 
8 import ddsp.effect.effect;
9 import ddsp.util.functions;
10 
11 import dplug.core.nogc;
12 
13 /**
14 * A general purpose Digital Delay with support for external feedback,
15   fractional delay, and feedback path effects.
16 */
17 class DigitalDelay(T) : AudioEffect!T
18 {
19     import core.stdc.stdlib : malloc, free;
20     import core.stdc.string : memset;
21     import dplug.core.vec;
22     
23 public:
24 
25     this() nothrow @nogc
26     {
27         buffer = null;
28         _size = 0;
29         _feedback = 0.0f;
30         _mix = 0.0f;
31         _useExternalFeedback = false;
32         
33         _readIndex = 1;
34         _writeIndex = 0;
35         
36         feedbackFX = makeVec!(AudioEffect!T);
37     }
38     
39     ~this() nothrow @nogc { free(buffer); }
40     
41     /// Set the sample rate and initialize the buffer.
42     override void setSampleRate(float sampleRate) nothrow @nogc
43     {
44         _sampleRate = sampleRate;
45         _feedback = 0;
46         _mix = 0;
47         _size = cast(uint)sampleRate + 1;
48 
49         if(buffer != null)
50             free(buffer);
51 
52         buffer = cast(float*) mallocSlice!float(_size);
53         reset();
54     }
55     
56     /// calculates and sets the number of samples required.
57     void setDelay(float msDelay) nothrow @nogc
58     {
59         _delayInSamples = msToSamples(msDelay, _sampleRate);
60         if(_delayInSamples > _size)
61         {
62             _size = cast(size_t)_delayInSamples + 1;
63             resizeOnNextReset = true;
64             reset();
65         }
66         else
67         {
68             resetIndices();
69         }
70     }
71 
72     /// Sets delay time, feedback, and mix
73     void setParams(float msDelay, float feedback, float mix) nothrow @nogc
74     {
75         setDelay(msDelay);
76         setFeedbackAmount(feedback);
77         setMixAmount(mix);
78     }
79     
80     override T getNextSample(const T input) nothrow @nogc
81     {
82         float xn = input;
83         float yn = buffer[cast(size_t)_readIndex];
84         
85         if(_readIndex == _writeIndex && _delayInSamples < 1.0f)
86         {
87             yn = xn;
88         }
89         
90         size_t _readIndex_1 = cast(size_t)(_readIndex - 1);
91         if(_readIndex_1 < 0)
92             _readIndex_1 = _size - 1;
93         
94         float yn_1 = buffer[_readIndex_1];
95         
96         float fracDelay = _delayInSamples - cast(int)_delayInSamples;
97         
98         float interp = linearInterp(0, 1, yn, yn_1, fracDelay);
99         
100         if(_delayInSamples == 0)
101             yn = xn;
102         else
103             yn = interp;
104         
105         if(!_useExternalFeedback)
106             buffer[cast(size_t)_writeIndex] = xn + _feedback * yn;
107         else
108             buffer[cast(size_t)_writeIndex] = xn + _feedbackIn * _feedback;
109         
110         float output = _mix * yn + (1.0 - _mix) * xn;
111         
112         if(++_writeIndex >= _size)
113             _writeIndex = 0;
114         if(++_readIndex >= _size)
115             _readIndex = 0;
116             
117         return output;
118     }
119     
120     //set all elements in the buffer to 0, and reset indices.
121     override void reset() nothrow @nogc
122     {
123         if(resizeOnNextReset) {
124             resizeOnNextReset = false;
125             buffer = cast(float*) mallocSlice!float(_size);
126         }
127         memset(buffer, 0, _size * float.sizeof);
128 
129         resetIndices();
130     }
131 
132     void resetIndices() nothrow @nogc
133     {
134         _readIndex = cast(long)(_writeIndex - cast(long)_delayInSamples);
135         if(_readIndex < 0)
136             _readIndex += _size;
137     }
138     
139     float getCurrentFeedbackOutput() nothrow @nogc { return _feedback * buffer[cast(size_t)_readIndex]; }
140     
141     float getFeedbackAmount() nothrow @nogc { return _feedback; }
142     
143     float getMixAmount() nothrow @nogc { return _mix; }
144     
145     void setCurrentFeedbackInput(float f) nothrow @nogc { _feedbackIn = f; }
146     
147     void setUseExternalFeedback(bool b) nothrow @nogc { _useExternalFeedback = b; }
148     
149     void addFeedbackEffect(AudioEffect!T effect) nothrow @nogc { feedbackFX.pushBack(effect); }
150     
151     void setFeedbackAmount(float feedback) nothrow @nogc { _feedback = feedback; }
152     
153     void setMixAmount(float mix) nothrow @nogc { _mix = mix; }
154 
155     /// used for debuging purposes.
156     override string toString()
157     {
158         import std.conv;
159         string output = "Rindex " ~ to!string(_readIndex) ~ " Windex " ~ to!string(_writeIndex) ~ " Size " ~ to!string(_size) ~ " Delay " ~ to!string(_delayInSamples);
160         return output;
161     }
162     
163 protected:
164 
165     float *buffer;
166     float _sampleRate;
167     float _feedback;
168     float _mix;
169     
170     size_t _size;
171     float _delayInSamples;
172     float _delayInMS;
173     
174     long _writeIndex;
175     long _readIndex;
176     
177     bool _useExternalFeedback;
178     bool resizeOnNextReset;
179     float _feedbackIn;
180     
181     Vec!(AudioEffect!T) feedbackFX;
182 
183     float maxDelayTime = 3.0f;
184 
185 }
186 
187 unittest
188 {
189     import dplug.core.nogc;
190     
191     DigitalDelay!float d = mallocNew!(DigitalDelay!float)();
192     //d.initialize(44100);
193     //d.update(22050, 0.5, 0.5);
194     //testEffect(d, "DDelay", 44100 * 2, false);
195 }