[sharing for requesting help] trying to make a feedback delay network in gen~

    May 19 2014 | 6:41 pm
    Hey, Feedback delay network is documented here : https://ccrma.stanford.edu/~jos/cfdn/Feedback_Delay_Networks.html It seems to be like a lot of delay lines in parallel that are fed back into each other. My skills in math/dsp are limited, so i assumed that the matrix at the top of the figure "Order 3 Feedback Delay Network." is just meaning that all sounds are summed up. But maybe i'm wrong ?.. There is a really cool fdn~ object in PureDatak, i'm trying to "replicate" it in gen~. Of course as the pd one has inners guts that create (nearly) any number of delay lines, it's not the same, but it still can be cool i think. I'm not sure how to do this. Is it like this ?

    • Jun 06 2014 | 10:36 pm
      Noone, really ? :/... is that topic complicated for everyone, or am i really on that much of a bad track ?..
    • Jun 08 2014 | 5:18 am
      your patch seems simple enough, but i'm not sure if it really follows the math described at the jos link... so i'm trying to read and understand that link at my leisure(it does seem a bit more complicated at first glance), and if i eventually figure something helpful out, will certainly post here, as i find this thread very interesting.
      in the meantime, here's a bump to help get anyone else's advice :)
      (also, if anyone here had the c source code for the puredata fdn~ object and felt like attaching just that here(i couldn't find just the one file very easily without the rest of the whole source distro) or could link to that specific file on sourceforge, might help alot... but just an idea... sorry in advance for my ignorance about getting pd-source-code :p)
    • Jun 08 2014 | 6:09 am
      Also FWIW Pluggo for Live has a Feedback Network patch you might want to look at.
    • Jun 08 2014 | 11:11 am
      @raja : i'm trying to translate the fdn~ from Pd to max also, see that thread : https://cycling74.com/forums/trying-to-compile-only-2-errors-left/ but probably better not start from there, i completely bonked the source. The only useful things i learned with that so far is that -it seems possible, and -you need to enforce the variant -fnested-functions in the "other c flags" field.
      @Rick : let me link to the fdn~ source direclty, if i may ! http://zwizwa.be/darcs/creb/modules/fdn~.c and didn't know that pluggo thing there is also an example of feedbak delay network inside a reverb in gen~, in the example/gen folder. it's the gigaverb~. From that and other sources, i inferred that the math for feedback delay networks includes substracting some of the delay lines, and adding others and feed a different result in each feedback delay line, and maybe calculating feedback coefficients a little more carefully. So there is math needed and that simple attempt certainely is not close to that.
      Thanks ! (i try a lot.)
    • Jun 08 2014 | 5:59 pm
      "@Rick : let me link to the fdn~ source direclty, if i may ! http://zwizwa.be/darcs/creb/modules/fdn~.c"
      YES! this is what i wanted, not the library, but just that one file. thanks so much! will take a look...
    • Aug 30 2014 | 11:34 pm
      At first, got pretty far porting the fdn~.c source to msp. Actually heard the reverb sound, with damping capabilities and such... really nice. But it kept crashing when i sent it a 'reset' message, so i kept working on it, eventually breaking so many goddamned things, i can't tell where i'm at anymore(at least, now, i got it so it doesn't crash on instantiation ...but it makes no sound anymore :p).
      Some veteran can probably take the .c file here and figure it out in seconds, but I might resist more torment for a bit :D
      Sorry(but attached here is however far i got... at least some things specific to max are already in place there: instead of free/memset/etc. there's sysmem_newptr/etc., floats are all changed to doubles(i think... though i might've changed em back and lost track by now :p), arguments are taken in using A_GIMME(rather than individual floats).... probably many other things... worked on this for the past 18 hours to no avail.)
    • Aug 31 2014 | 12:51 am
      You're doing gods work again, raja.
    • Aug 31 2014 | 10:16 am
      wow. thanks a lot ... incidentally, i was working on it *a little* yesterday, and weeell i didn't even think about memset to system_newptr etc... now that i look at your source it seems a looot more work than what i had anticipated T_T
      if i may allow myself some questions... * you added fonction declarations for all the functions, is that just cleaner programming method or required in Max vs not in Pd ? * you removed all the "static" casting (if i understand well, those things are called typecasting right ?..) of functions, i don't understand why there where "static" to begin with... * i don't understand when you need to use t_double or double. * You melted together the fdnctl and fdn struct, is that required for Max vs Pd ? or again cleaner (or your prefered) programming workflow ?
    • Aug 31 2014 | 7:17 pm
      I'm no expert and this is just one exercise I thought would get me back into coding mspmexternals in C, but I'm very rusty... Will do my best to explain:
      - 'static' simply declares anything as global, but only within the .c file within which it appears. You normally don't need it for anything but the class name(ex: 'static t_class fdn_class') and you don't really need it there either(but I guess it can be safer in general(?)...) depends on how unique or generic your naming schemes are...
      - 'double' and 't_double' are essentially the same everywhere, but the 't_...' versions of things are type-definitions which typecast to something assured to be the same size on all machines... I've never been entirely clear on this difference... But, I think it has to do with the fact that certain data formats have different byte/word lengths to their binary equivalents on different architectures, and the typecasting 't_..' versions make sure they are always the same(for example 't_float' will always be a 32-bit float and 't_double' will always be a 64-bit float no matter how architectures and operating systems change in the future, and no matter their differences now too...)
      - re: function declarations, I always thought this is just how C works: you need a declaration called a 'function prototype' before all functions so that the compiler knows what functions it can expect to look for(I think this is why you can write all functions in any order you choose after that)... In PD, it could be that they put the function prototypes in a separate definition file, or maybe I'm missing something else.... ;p
      - melting the structs together: haha, no not required as far as I know, but when I can't trace problems very well, I end up trying to make the code look more like regular max/MSP objects(most of which only use one structure). Furthermore, in PD, just like in Max5 and earlier(32-bit versions), you used to pass the input signals along with samprate info. and reference to object itself all in one proxy-like thing from the DSP method but you could send whatever you wanted in place of the exact object struct reference(you can see there in the original PD version's c-file, within the function 'fdn_dsp', he passes the address of the fdnctl structure: "&x->x_ctl" instead of the main object structure). In Max's newer dsp64 method, this may still be possible, but since we get a pointer to the main object's structure itself, I figured I'd just melt them together for convenience of direct references in my DSP method(in a more recent version I reseparated the structs again, but in my perform routine, instead of pulling from the main object's struct, I added this line: "t_fdnctl *ctl = &x->x_ctl" thus making it work more like the original PD code...) Basically this part of the work was simply not knowing what was going wrong with crashes at first and guessing that the object struct might simplify things. Later, I found out that the crashes were more due to my way of initializing the newly created sysmem_newptr memory(when you use 'for' instead of 'memset', you don't need to account for the bit-size of the data being stored... no need to use 'sizeof' there... At first, I did so and when this would initialize memory outside the range I wanted, it ended up 'corrupting the heap'(
      Incidentally, I also tried returning memory management to the original malloc/memset/free way, and that works fine too(slowly I've figured out the crashes, and I'm realizing that my main prob with sound is probably directly in the perform routine, or in some silly small mistake I've made somewhere initializing DSP... But I'm taking a small break now, because debugging this stuff is so much harder than in gen~ or even in some coding environs like supercollider, and it can get a bit overwhelmingly irritating, teeeheeee! >;D)
      edit: duh... i should've just used 'sysmem_newptrclear' :p will try later: https://cycling74.com/sdk/MaxSDK-6.0.4/html/group__memory.html#ga1c178a079247f715c6e34c828d375324 Anyways, hope that helps a bit for now... will keep at it from time to time.
    • Aug 31 2014 | 8:40 pm
      ...also... some thoughts on doing this in gen~ since this thread was originally about that: -it's very possible to do basic variations of the idea on this page: https://ccrma.stanford.edu/~jos/cfdn/Feedback_Delay_Networks.html seems to me though, this(particularly the J.Stautner and M.Puckette paragraph) is also important gound for understanding that: https://ccrma.stanford.edu/~jos/cfdn/Prior_Work.html
      -there's even variations of the feedback delay network idea in the gen~ examples(freeverb and gigaverb... though, i must admit i haven't studied them extensively)
      -i have to study the pd version of fdn~ more, but it seems possible and easier in gen~ to do that exact idea if you don't try to incorporate some of the messaging to change internal states like 'lines' message(which changes the number of delay lines/'order'(very possible in gen~ if you have an absolute max of lines to begin with but not as efficient...(?))) there are also efficient c tricks i don't know yet about implementing in gen~(it's possible codebox/genexpr can work with hexadecimal(?)), for example, this line ensures no matter what order(#-of-delay-lines) the user sets the object to, it will always be a multiple of 4:int order = argc & 0xfffffffc; (hence Tom Schouten's comment in dutch: "veelvoud van 4"('multiple of 4') in the original c file ;D) and finally, i barely understand the equation for the first order IIR filter, but then to get to the difference equation is amazing... must learn math better :p furthermore, it's used to setup the gain coefficients but not at sample-rate... so... there would be some need to think more about messages/params to gen~ separately(or maybe i'm just whining to myself :p)
      ________________ ...and i didn't mention what causes the object to output no sound now because i have no clue... but soon, because it's safer to dereference an msp object's struct entirely within its perform_routine before the vector is parsed sample-by-sample(before 'for' or 'while' loop),..i might try more of that... the original PD code doesn't do this entirely, these lines in particular:
      cvec = ctl->c_vector[ctl->c_curvector];
       lvec = ctl->c_vector[ctl->c_curvector ^ 1];
       ctl->c_curvector ^= 1;
    • Aug 31 2014 | 9:37 pm
      nice ! thanks for the infos. re:functions declarations , from what i understood one needs to declare a function before it is used in another function - but the header lines are only a direct reference to the definition, so it counts as declaration (the compiler sees the header, then looks for function def in the code i guess, before going to next header) ; in the end if you define all the functions before they are used it should be fine ? :) And i completely get the "overwhelmingly irritating" part, although it will be a delight to know about your progression in this thing ;D
    • Aug 31 2014 | 11:59 pm
      in the end if you define all the functions before they are used it should be fine ?
      ya, exactly. i just reread the relevant info. in Eric Lyon's book, the compiler assumes all functions will return an 'int'. this is why the 'main' function doesn't require a prototype(the compiler uses function prototypes to do argument checking(type and number-of), 'main' has no arguments and returns an 'int'). prototypes might not always be necessary but are particularly required for max external code.
    • Sep 01 2014 | 11:26 am
      boy do i feel stupid.
      i had a typo in the "fdn_dsp64" method... for the symbol there i wrote "dspadd64" where it should've been "dsp_add64"(with an underscore... and xcode doesn't show warnings because it's a symbol particular to the max api)
      so now i've got the object fully working... but just a warning: seems a bit volatile in terms of how easily the delay lines blow up when changing 'hi' and 'lo'. keep the volume low until you get a feel for how fast it can change. also keep the volume low when using the 'lines' message or when reinstantiating the object with new arguments. (use the 'print' message to see how the internal states of the object change after messaging...)
      some notes on what i did and other things:
      • i haven't tried the 'lin' and 'exp' messages yet... i leave those for someone else to test and fix if necessary :p
      • could make this backward compatible for max5 by including an older dsp perform routine(see 'simplemsp' examp in the sdk folder)... could also make a windows version... i leave these to someone else :)
      • malloc/memset/free is changed to sysmem_newptrclear/'for'-loop for the 'reset' message/sysmem_freeptr
      • assist for inlets and outlets added
      • in Max6, signals are 64-bit, but regular messaging to objects(according to eric lyon's .pdf update to his book later uploaded here on the forums) still use 32-bit where floats are concerned (?) so i tried changing the arguments for functions which matched messages all to 't_float', this didn't change anything, but then finally, while i left everything else at t_double, i changed the maximum buffer size(2nd argument to object) to register as a float argument instead of double("t_float maxbufsz;") and this improved the response of the 'hi'/'lo' messages to the object in general(whereas before i did this, the 'hi'/'lo' messages would cause a more abrupt/noisy change and i don't think that's how the original object was supposed to sound... nice to figure out this added detail at the last minute :).
      • the delay sounds kind of rough in the highs...(tinny... distorted a bit...)... the overall delay-line read could probably use some interpolation... and the 'hi'/'lo' messages could be turned into signal inputs with more interpolation there as well(?)
      • i removed the 'fdn_updatedamping' function entirely... it basically just leads to 'fdn_time' so i simply call that directly wherever fdn_updatedamping used to exist
      • i removed many unused variables(probably either meant for debugging or for future plans... example: "t_float input" and "t_float output" in the original code's t_fdnctl struct)
      • within the function "fdn_setupdeline", i added "CLAMP" to keep the 'sum' variable from going above 'mask' where it would cause 'instability'
      • i made a comment before about needing to dereference the object struct within the perform_routine before the interrupt loop.. saying the original PD code didn't do this... i was mistaken, it is dereferenced already :p too many thoughts going on at once, not enough time taken to write them out(though my words are always longer than necessary, hehe ;D)
      • i made a comment before about finding debugging overwhelmingly irritating sometimes... but more irritating is how stupid it is to try different ways of writing the same code and then forget to enter a simple underscore in the right place :p ...but extremely satisfying when you finally figure it out and it turns out not to be something major or complex :D
      ok then. attached here(for 64-bit max6 on osx only... someone else can take the .c code from here to windows, etc...). enjoy :)
    • Sep 01 2014 | 11:51 am
      this is greater than awesome thanks so much !! soooooo cool !!!
    • Sep 01 2014 | 3:08 pm
      lin and exp seem to work as they should here !
    • Sep 01 2014 | 7:29 pm
      ^oh nice! Bonus win, haha, glad to hear they work too. You're welcome. Thanks for giving me a good way to refresh my amateur external/c-coding chops. It's a nice feeling to get it working :)
    • Sep 01 2014 | 11:27 pm
      ha, another stupid mistake on my part (so sorry!)
      in the .c file the fdn_setupdeline method should have this CLAMP instead:sum += CLAMP( (int)(length[t-1] * scale), 0, mask);
      (in my previous attachment, the max reach of clamp is set to 'mask-sum'... ugh...) you'll want to change that(...or change the code to the original PD version there...) and recompile, i will avoid polluting the forum with more attachments :p
    • Sep 01 2014 | 11:35 pm
      ...but you'll pollute us with attachments once things start working out, right?
    • Sep 02 2014 | 3:37 am
      @awetterbergpostcybercitydk aka Wetterberg
      Not exactly: there are no serious crashy bugs anymore, and the object is behaving nicely(my previously posted error is a fix folks can perform themself, it doesn't hurt to leave it as is in my last attachment, but the clamp change I mentioned will give the hi/lo settings to the object overall a bit more range...)...
      But ya, as of my last attachment("FDN4MSPOSX.zip") things are all working out nicely...(check out the external(OSX) if ya haven't already, there's a helpfile in there to make it a lil easier. It's all done :)
      My effort here was just to make it easier for us to look at fdn~ in max(it's more for purposes of vichug's thread of inquiry, not so much a full on developer-style release to the public). I don't want to make it too easy for people to take Tom Schouten's brilliant fdn~ code without being pushed a lil to study it. So the last correction I mentioned keeps this MSP port imperfect until folks themselves try to correct and compile it their self. I pray it does not upset you: think of it like this is my way of keeping it on the point of getting to know Schouten's code rather than making it easy to just rip and use it.
      It comes from my experience in a previous life... So please, grab a cup of tea and a bag of popcorn and I shall tell you a little story: You see, i existed as a humble Japanese sword-smith, around the time of Tokugawa shogunate in a small temple known as 'Sainenji'. I had perfected my master's patient practice of folding steel into the finest blades by a process which kept the blades hot during the manufacturing process for weeks on end(this had the effect of reducing small fractures that can sometimes occur during the natural air cooling process when the steel has not been folded over and over at least 100 times yet). Over the course of weeks, we would fold the steel over and over, each blade had to be folded at least 1000 times to ensure the greatest strength. During the course of my training as apprentice and later as an accomplished swordsmith, myself and my fellow craftsman as well as some of the monks at the temple would talk about life, and we would observe the slow processes which would unfold in all aspects of nature. At one point we had crafted approximately 500 swords all kept within our unlocked workshop. One night while we were all at meditation, a local gang of ninjas crept in and stole the surplus of swords we had kept laying around. They later used them against us, slaying many of my own friends and family in front of me, all with swords I had painstakingly crafted with my own hands. And as the blood of my kin spattered across my stunned face, i saw it as if in slow motion: how long it can take to create something like life itself, and yet how quickly something can be used to destroy, especially if the pain of creation is not appreciated first hand by experience. These ninjas slaughtered pregnant women and small children without a second thought, they were trained merely to appreciate the efficacy with which they could kill. If they had experienced the beauty of time as it unfolded with each new fold of steel that went into the strength of their weaponry, if they had experienced the joy and friendships that surrounded the temple as we worked peacefully to perfect a craft which was more about the function of grace and courage in human movement as it finds a relationship with the math behind the gravity and momentum of a sword, rather than how easily it can be used to end a life, they would not have killed my people so quickly, so cold heartedly.
      And so it is true to this day: I wear the imperfections of my creations like a bleeding heart on my shoulder, to help point the way to true experience, the kind gained by sacrifice, empathy, and arduous toil and painful struggle! So that no one can take for granted what it means to truly----
      Nah fuck it, I'm just bullshitting >;D
      But seriously though, give the correction a shot and compile it yourself, and if you have probs, let me know again here and I'll attach it myself with the latest small correction I mentioned :)
      For the honor and glory of Tom Schouten, the master samurai swordsmith whose original brilliance is the sole provision for our enlightenments here!
    • Sep 02 2014 | 5:09 am
      You make me become more puzzled about fdn.
    • Sep 02 2014 | 10:49 am
      So you're saying every samouraï should be a swordsmith as well ? >:D
      On a more related note, i noticed that when i try something like lin 32 10 1015, or anything with a long delay line, it doesn't seem to work well. Well i'm not sure how it should work, but sound fades all of a sudden. Is it related to the clamp mistake you were talking about ? It's a behaviour that i encountered in PD as well. If this is a mistake in the original PD external, it could be maybe worht it to send the updated version to the pd list or whatever ?
      hm just gave a look at original pd c file and they don't have this ClAMP function, neither the typo you made. But maybe adding a clamp would be beneficial for the pd version too ? maybe the clamping would be necessary for the bug i'm talking about too ?
    • Sep 02 2014 | 6:14 pm
      ^ya, the clamp is added by me to avoid the error message about 'instability' after that which would happen more easily without the clamp in place(I don't like to leave things easily unstable so I added clamp). You can take it out entirely and it would be more close to the original PD.
      I don't know about 'lin' and 'exp' messages. I didn't change em at all. If it works the same in PD that's probably how it works... These messages basically use 3 arguments to determine the number of delay lines, the minimum delay line length, and the maximum length('lin' will create lengths in a linear fashion, 'exp' will create them exponentially related...). The two length arguments are in milliseconds.
      If you take out the clamp I added it might improve, you might try that before sending to the PD list... But if you're saying it works this way in PD, it could be that this is just how it works... (Only other prob I could imagine is maybe in your Lin message, creating 32 lines might be too much? Doesn't seem like too much to me, but I haven't really done enough A/B comparisons of the PD version with this one to say for sure...)
      It does appear that the original fdn~ was not entirely finished: There is also the 'prime number' calculation code which is near the top of the original PD .c code. This was commented out in the PD and never used so I simply left it out. I believe he was making this to be able to calculate better lengths(look at the fdn_new method in his original .c file, you'll notice under the "/*preset*/" comment area, he initializes the first four delay lines with prime number lengths(29, 31, 37, 67)....) If this were also added properly(I don't understand it fully), it might also improve the same behavior you're noticing.
      Anyways, hope that helps make some more sense out of it, I'll leave it for you to decide whether or not to connect with the Pd community about it(I don't think my clamp is the only way to protect against the instability that was left in there, so you might experiment more first... But... It seems because PD is developed voluntarily/open-source there are sometimes these things left in the code that are either incomplete or cause instability(like this one where it could be trying to access memory outside of range if left without my clamp addition)).
      "...should be a swordsmith as well?"
      Yes, yes I am. In this day and age, if you had to make every bullet you shot at another person from scratch, you'd think twice more before wasting them so easily on any frivolous kills. Every digital-artist should be a developer/programmer as well :)