1 import std.stdio;
2 import std.format;
3 import std.conv;
4 import std.string;
5 import std.algorithm : max, min, map;
6 import core.stdc.stdio : snprintf;
7 import core.stdc.stdlib;
8 import core.stdc.string : memset, strlen;
9 import core.stdc.math : isinf;
10 import dplug.core.vec;
11 import dplug.core.map;
12 import dplug.core.nogc;
13 import core.stdc.stdint : uintptr_t;
14 
15 alias FAUSTFLOAT = double;
16 
17 class Meta {
18 nothrow:
19 @nogc:
declare(string name,string value)20     void declare(string name, string value) {}
21 }
22 
23 class UI {
24 nothrow:
25 @nogc:
declare(string id,string key,string value)26     void declare(string id, string key, string value){ }
declare(int id,string key,string value)27     void declare(int id, string key, string value){ }
declare(FAUSTFLOAT * id,string key,string value)28     void declare(FAUSTFLOAT* id, string key, string value){ }
29 
30     // -- layout groups
31 
openTabBox(string label)32     void openTabBox(string label){ }
openHorizontalBox(string label)33     void openHorizontalBox(string label){ }
openVerticalBox(string label)34     void openVerticalBox(string label){ }
closeBox()35     void closeBox(){ }
36 
37     // -- active widgets
38 
addButton(string label,FAUSTFLOAT * val)39     void addButton(string label, FAUSTFLOAT* val){ }
addCheckButton(string label,FAUSTFLOAT * val)40     void addCheckButton(string label, FAUSTFLOAT* val){ }
addVerticalSlider(string label,FAUSTFLOAT * val,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step)41     void addVerticalSlider(string label, FAUSTFLOAT* val, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step){ }
addHorizontalSlider(string label,FAUSTFLOAT * val,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step)42     void addHorizontalSlider(string label, FAUSTFLOAT* val, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step){ }
addNumEntry(string label,FAUSTFLOAT * val,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step)43     void addNumEntry(string label, FAUSTFLOAT* val, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step){ }
44 
45     // -- passive display widgets
46 
addHorizontalBargraph(string label,FAUSTFLOAT * val,FAUSTFLOAT min,FAUSTFLOAT max)47     void addHorizontalBargraph(string label, FAUSTFLOAT* val, FAUSTFLOAT min, FAUSTFLOAT max){ }
addVerticalBargraph(string label,FAUSTFLOAT * val,FAUSTFLOAT min,FAUSTFLOAT max)48     void addVerticalBargraph(string label, FAUSTFLOAT* val, FAUSTFLOAT min, FAUSTFLOAT max){ }
49 }
50 
51 interface dsp {
52 nothrow:
53 @nogc:
54 public:
55     void metadata(Meta* m);
56     int getNumInputs();
57     int getNumOutputs();
58     void buildUserInterface(UI* uiInterface);
59     int getSampleRate();
60     void instanceInit(int sample_rate);
61     void instanceResetUserInterface();
62     void compute(int count, FAUSTFLOAT*[] inputs, FAUSTFLOAT*[] outputs);
63     void initialize(int sample_rate);
64 }
65 
66 enum int kFrames = 64;
67 
68 //----------------------------------------------------------------------------
69 // FUI
70 //----------------------------------------------------------------------------
71 class FUI : UI
72 {
73 nothrow:
74 @nogc:
75 
76     protected:
77 
78         Vec!string fControlsLevel;
79         Vec!(FAUSTFLOAT*) fButtons;
80 
81         // add an element by relating its full name and memory zone
82         void addElement(string label, FAUSTFLOAT* zone, bool button = false)
83         {
84             if (button) {
85                 fButtons.pushBack(zone);
86             }
87         }
88 
89 
90     public:
91 
this()92         this()
93         {
94             fControlsLevel = makeVec!string();
95             fButtons = makeVec!(FAUSTFLOAT*)();
96         }
~this()97         ~this() {}
98 
setButtons(bool state)99         void setButtons(bool state)
100         {
101             for (size_t i = 0; i < fButtons.length(); i++) {
102                 *fButtons[i] = state;
103             }
104         }
105 
pushLabel(string label)106         void pushLabel(string label) { fControlsLevel.pushBack(label); }
popLabel()107         void popLabel() nothrow @nogc { if(fControlsLevel.length() > 0) fControlsLevel.popBack(); }
108 
109         // -- widget's layouts (just keep track of group labels)
110 
openTabBox(string label)111         override void openTabBox(string label) { pushLabel(label); }
openHorizontalBox(string label)112         override void openHorizontalBox(string label) { pushLabel(label); }
openVerticalBox(string label)113         override void openVerticalBox(string label) { pushLabel(label); }
closeBox()114         override void closeBox() nothrow @nogc { popLabel(); }
115 
116         // -- active widgets (just add an element)
117 
addButton(string label,FAUSTFLOAT * zone)118         override void addButton(string label, FAUSTFLOAT* zone) { addElement(label, zone, true); }
addCheckButton(string label,FAUSTFLOAT * zone)119         override void addCheckButton(string label, FAUSTFLOAT* zone) { addElement(label, zone); }
addVerticalSlider(string label,FAUSTFLOAT * zone,FAUSTFLOAT,FAUSTFLOAT,FAUSTFLOAT,FAUSTFLOAT)120         override void addVerticalSlider(string label, FAUSTFLOAT* zone, FAUSTFLOAT, FAUSTFLOAT, FAUSTFLOAT, FAUSTFLOAT)
121                                                                     { addElement(label, zone); }
addHorizontalSlider(string label,FAUSTFLOAT * zone,FAUSTFLOAT,FAUSTFLOAT,FAUSTFLOAT,FAUSTFLOAT)122         override void addHorizontalSlider(string label, FAUSTFLOAT* zone, FAUSTFLOAT, FAUSTFLOAT, FAUSTFLOAT, FAUSTFLOAT)
123                                                                     { addElement(label, zone); }
addNumEntry(string label,FAUSTFLOAT * zone,FAUSTFLOAT,FAUSTFLOAT,FAUSTFLOAT,FAUSTFLOAT)124         override void addNumEntry(string label, FAUSTFLOAT* zone, FAUSTFLOAT, FAUSTFLOAT, FAUSTFLOAT, FAUSTFLOAT)
125                                                                     { addElement(label, zone); }
126 
127         // -- passive widgets (are ignored)
128 
addHorizontalBargraph(string label,FAUSTFLOAT *,FAUSTFLOAT,FAUSTFLOAT)129         override void addHorizontalBargraph(string label, FAUSTFLOAT*, FAUSTFLOAT, FAUSTFLOAT) {}
addVerticalBargraph(string label,FAUSTFLOAT *,FAUSTFLOAT,FAUSTFLOAT)130         override void addVerticalBargraph(string label, FAUSTFLOAT*, FAUSTFLOAT, FAUSTFLOAT) {}
131 
132         // -- soundfiles
133         // override void addSoundfile(string label, string filename, Soundfile** sf_zone) {}
134 
135         // -- metadata are not used
136 
declare(FAUSTFLOAT *,string,string)137         override void declare(FAUSTFLOAT*, string, string) {}
138 }
139 
140 //----------------------------------------------------------------------------
141 // DSP control UI
142 //----------------------------------------------------------------------------
143 
144 class CheckControlUI : UI
145 {
146 nothrow:
147 @nogc:
148     Map!(FAUSTFLOAT*, FAUSTFLOAT) fControlZone;
149 
addButton(string label,FAUSTFLOAT * zone)150     override void addButton(string label, FAUSTFLOAT* zone)
151     {
152         addItem(zone, FAUSTFLOAT(0));
153     }
addCheckButton(string label,FAUSTFLOAT * zone)154     override void addCheckButton(string label, FAUSTFLOAT* zone)
155     {
156         addItem(zone, FAUSTFLOAT(0));
157     }
addVerticalSlider(string label,FAUSTFLOAT * zone,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step)158     override void addVerticalSlider(string label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step)
159     {
160         addItem(zone, init);
161     }
addHorizontalSlider(string label,FAUSTFLOAT * zone,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step)162     override void addHorizontalSlider(string label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step)
163     {
164         addItem(zone, init);
165     }
addNumEntry(string label,FAUSTFLOAT * zone,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step)166     override void addNumEntry(string label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step)
167     {
168         addItem(zone, init);
169     }
170 
addItem(FAUSTFLOAT * zone,FAUSTFLOAT init)171     void addItem(FAUSTFLOAT* zone, FAUSTFLOAT init)
172     {
173         fControlZone[zone] = init;
174     }
175 
checkDefaults()176     bool checkDefaults()
177     {
178         foreach(kv; fControlZone.byKeyValue())
179         {
180             if(*(kv.key) != kv.value) return false;
181         }
182         return true;
183     }
184 
initRandom()185     void initRandom()
186     {
187         foreach(kv; fControlZone.byKeyValue())
188         {
189             kv.value = 0.123456789;
190         }
191     }
192 }
193 
printHeader(ref string irFile,mydsp DSP,int nbsamples)194 static void printHeader(ref string irFile, mydsp DSP, int nbsamples)
195 {
196     // Print general informations
197     irFile ~= format!"number_of_inputs  : %3d\n"(DSP.getNumInputs());
198     irFile ~= format!"number_of_outputs : %3d\n"(DSP.getNumOutputs());
199     irFile ~= format!"number_of_frames  : %6d\n"(nbsamples);
200 }
201 
normalize(FAUSTFLOAT f)202 static FAUSTFLOAT normalize(FAUSTFLOAT f)
203 {
204     if (std.math.isNaN(f)) {
205         stderr.writeln("ERROR : isnan\n");
206         throw new Exception("ERROR : isnan\n");
207     } else if (isinf(f)) {
208         stderr.writeln("ERROR : isinf\n");
209         throw new Exception("ERROR : isinf\n");
210     }
211     return (fabs(f) < FAUSTFLOAT(0.000001) ? FAUSTFLOAT(0.0) : f);
212 }
213 
real_channels(T)214 class real_channels(T)
215 {
216     private:
217 
218         int fNumFrames;
219         int fNumChannels;
220         T*[] fBuffers;
221         T*[] fSliceBuffers;
222 
223     public:
224 
225         this(int nframes, int nchannels)
226         {
227             fBuffers = new T*[nchannels];
228             fSliceBuffers = new T*[nchannels];
229             fNumFrames = nframes;
230             fNumChannels = nchannels;
231 
232             // allocate audio channels
233             for (int chan = 0; chan < fNumChannels; chan++) {
234                 fBuffers[chan] = cast(T*)new FAUSTFLOAT[fNumFrames];
235             }
236 
237             zero();
238         }
239 
240         void zero()
241         {
242             // clear audio channels
243             for (int chan = 0; chan < fNumChannels; chan++) {
244                 memset(fBuffers[chan], 0, T.sizeof * fNumFrames);
245             }
246         }
247 
248         void impulse()
249         {
250             // set first sample to 1 for all channels
251             for (int chan = 0; chan < fNumChannels; chan++) {
252                 fBuffers[chan][0] = FAUSTFLOAT(1.0);
253                 for (int frame = 1; frame < fNumFrames; frame++) {
254                     fBuffers[chan][frame] = T(0.0);
255                 }
256             }
257         }
258 
259         void display()
260         {
261             for (int chan = 0; chan < fNumChannels; chan++) {
262                 for (int frame = 0; frame < fNumFrames; frame++) {
263                     writeln("chan = " ~ to!string(chan) ~ " frame = " ~ to!string(frame) ~ " value = " ~ to!string(fBuffers[chan][frame]) ~ "\n");
264                 }
265             }
266         }
267 
268         // GC handles free the buffers in this case, nothing to do here
269         ~this()
270         {
271         }
272 
273         T*[] buffers() { return fBuffers; }
274 
275         T*[] buffers(int index)
276         {
277             assert(index < fNumFrames);
278             for (int chan = 0; chan < fNumChannels; chan++) {
279                 fSliceBuffers[chan] = &fBuffers[chan][index];
280             }
281             return fSliceBuffers;
282         }
283 
284 }
285 
286 class channels : real_channels!FAUSTFLOAT {
287 
288     public:
289 
this(int nframes,int nchannels)290         this(int nframes, int nchannels) { super(nframes, nchannels); }
291 }
292 
293 // To be used in static context
294 static void runDSP(ref string irFile, mydsp DSP, ref const string file, ref int linenum, int nbsamples, bool inpl = false, bool random = false)
295 {
296     FUI finterface = new FUI();
297     DSP.buildUserInterface(cast(UI*)&finterface);
298 
299     // // Soundfile
300     // TestMemoryReader memory_reader;
301     // SoundUI sound_ui = new SoundUI("", -1, &memory_reader);
302     // DSP.buildUserInterface(&sound_ui);
303 
304     // Get control and then 'initRandom'
305     CheckControlUI controlui = new CheckControlUI();
306     DSP.buildUserInterface(cast(UI*)&controlui);
307     controlui.initRandom();
308 
309     // // MIDI control
310     // midi_handler handler;
311     // MidiUI midi_ui = new MidiUI(&handler);
312     // DSP.buildUserInterface(&midi_ui);
313 
314     // Init signal processor and the user interface values
315     DSP.initialize(44100);
316 
317     // Check getSampleRate
318     if (DSP.getSampleRate() != 44100) {
319         stderr.writeln("ERROR runDSP in getSampleRate : " ~ to!string(DSP.getSampleRate()) ~ "\n");
320     }
321 
322     // Check default after 'init'
323     if (!controlui.checkDefaults()) {
324         stderr.writeln("ERROR runDSP in checkDefaults after 'init'\n");
325     }
326 
327     // Check default after 'instanceResetUserInterface'
328     controlui.initRandom();
329     DSP.instanceResetUserInterface();
330     if (!controlui.checkDefaults()) {
331         stderr.writeln("ERROR runDSP in checkDefaults after 'instanceResetUserInterface'\n");
332     }
333 
334     // Check default after 'instanceInit'
335     controlui.initRandom();
336     DSP.instanceInit(44100);
337     if (!controlui.checkDefaults()) {
338         stderr.writeln("ERROR runDSP in checkDefaults after 'instanceInit'\n");
339     }
340 
341     // To test that instanceInit properly init a cloned DSP
342     DSP = DSP.clone();
343     DSP.instanceInit(44100);
344 
345     // Init UIs on cloned DSP
346     DSP.buildUserInterface(cast(UI*)&finterface);
347     // DSP.buildUserInterface(cast(UI)&sound_ui);
348     // DSP.buildUserInterface(cast(UI)&midi_ui);
349 
350     int nins = DSP.getNumInputs();
351     int nouts = DSP.getNumOutputs();
352 
353     channels ichan = new channels(kFrames, ((inpl) ? max(nins, nouts) : nins));
354     channels ochan = (inpl) ? ichan : new channels(kFrames, nouts);
355 
356     int run = 0;
357 
358     // // Test MIDI control
359     // for (int i = 0; i < 127; i++) {
360     //     handler.handleData2(0, MidiStatus.MIDI_CONTROL_CHANGE, 0, i, 100);
361     //     handler.handleData2(0, MidiStatus.MIDI_POLY_AFTERTOUCH, 0, i, 75);
362     //     handler.handleData2(0, MidiStatus.MIDI_NOTE_ON, 0, i, 75);
363     //     handler.handleData2(0, MidiStatus.MIDI_NOTE_OFF, 0, i, 75);
364     //     handler.handleData2(0, MidiStatus.MIDI_PITCH_BEND, 0, i, 4000);
365     // }
366     // handler.handleData1(0, MidiStatus.MIDI_PROGRAM_CHANGE, 0, 10);
367     // handler.handleData1(0, MidiStatus.MIDI_AFTERTOUCH, 0, 10);
368 
369     // GUI::updateAllGuis();
370 
371     // print audio frames
372     int i;
373     try {
374         while (nbsamples > 0) {
375             if (run == 0) {
376                 ichan.impulse();
377                 finterface.setButtons(true);
378             }
379             if (run >= 1) {
380                 ichan.zero();
381                 finterface.setButtons(false);
382             }
383             int nFrames = min(kFrames, nbsamples);
384 
385             if (random) {
386                 int randval = rand();
387                 int n1 = randval % nFrames;
388                 int n2 = nFrames - n1;
389                 DSP.compute(n1, ichan.buffers(), ochan.buffers());
390                 DSP.compute(n2, ichan.buffers(n1), ochan.buffers(n1));
391             } else {
392                 DSP.compute(nFrames, ichan.buffers(), ochan.buffers());
393             }
394 
395             run++;
396             // Print samples
397             for (int j = 0; j < nFrames; j++) {
398                 irFile ~= format!"%6d : "(linenum++);
399                 for (int c = 0; c < nouts; c++) {
400                     FAUSTFLOAT f = normalize(ochan.buffers()[c][j]);
401                     irFile ~= format!" %8.6f"(f);
402                 }
403                 irFile ~= "\n";
404             }
405             nbsamples -= nFrames;
406         }
catch(Exception ex)407     } catch (Exception ex) {
408         stderr.writeln("ERROR in '" ~ file ~ "' at line : " ~ to!string(i) ~ "\n");
409     }
410 
411     ichan.destroy();
412     if (ochan != ichan) ochan.destroy();
413     DSP.destroy();
414 }
415 
416 
main(string[]args)417 void main(string[] args)
418 {
419     if(args.length < 2)
420     {
421         stderr.writeln("Error: no argument provided for IR file path");
422         return;
423     }
424 
425     string irFilePath = args[1];
426     // string irFilePath = "/home/reker/.test/test.ir";
427     string fileText = "";
428     File irFile = File(irFilePath, "w+");
429     scope(exit)
430     {
431         irFile.write(fileText);
432         irFile.close();
433     }
434 
435     int linenum = 0;
436     int nbsamples = 60_000;
437 
438     // print general informations
439     printHeader(fileText, new mydsp(), nbsamples);
440 
441     // linenum is incremented in runDSP and runPolyDSP
442     runDSP(fileText, new mydsp(), args[0], linenum, nbsamples/4);
443     runDSP(fileText, new mydsp(), args[0], linenum, nbsamples/4, false, true);
444     // runPolyDSP(new mydsp(), linenum, nbsamples/4, 4);
445     // runPolyDSP(new mydsp(), linenum, nbsamples/4, 1);
446 }
447 
448 <<includeIntrinsic>>
449 
450 <<includeclass>>
451