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