1. IMPORTANT:
    We launched a new online community and this space is now closed. This community will be available as a read-only resources until further notice.
    JOIN US HERE

Building guidelines?

Discussion in 'Building With Reaktor' started by joeking, Jan 12, 2018.

  1. joeking

    joeking NI Product Owner

    Messages:
    171
    Spaghetti - just enjoy it or do you choke? Is it too easy to create indigestible pasta (Programmess,Audio,Systems,Theory,Anti-patterns) with a simple gui? Icsends reduce lines but then make following the flow a bit more difficult, like a map with invisible roads.
    Is spaghetti a bad thing?

    Number of modules? Should you put them in a macro if you have, say, more than 5 visible?
    Should a connection be at the top or the bottom? Should inconsistency be consistent?
    Are there actually any visual building guidelines?
     
  2. mosaic_

    mosaic_ Guest

    I personally abhor spaghetti. I would want people to be able to extract and learn from my stuff reasonably easily. One of the reasons I like Core is that it's easy to cut down on wires by using (descriptively named, I hope) Quickbusses. The addition of bundles helps too. Sometimes, though, I just like the visual effect of a bunch of multicolored parallel wires.

    In Core, I usually align outputs to the top of each macro, but that varies too. I like arranging the innards of my Core Cells into neat cyberpunk blocks, and I'll twiddle with the alignment to facilitate that. I've looked at my old ensembles in the UL and thought of bringing them up to this standard, but there's more exciting work to do, I think... But it's like a little game for me, cleaning up my code. Maybe that's why I don't get anything done these days :D
     
  3. salamanderanagram

    salamanderanagram NI Product Owner

    Messages:
    3,454
    my general rules

    1) create as many completely straight wires as possible.
    2) create as few backwards moving wires (going from the right to the left) as possible.
    3) create 'dummy' inputs and outputs to macros and core cells if needed to follow these rules.
    4) any primary structure that makes looks ugly (IE, backwards wires) should be turned into a macro so you don't need to look at it often and it doesn't make everything around it hard to read.

    in core

    anything that might have a remotely ugly looking physical connection becomes a quickbus.
     
  4. Johnny-T

    Johnny-T New Member

    Messages:
    16
    Three ideas of mine:
    - Give constants names: 3.14 should be handled as pi...
    - Every group, that has a purpose, gets a name and a macro. Even, if there are only two elements.
    - Avoid global variables (scoped buses) and large scopes, even if they seem to be convenient.
     
  5. colB

    colB NI Product Owner

    Messages:
    3,969
    As I see it, there is no call/return mechanism in Reaktor no classes, no encapsulation, so the only way to share data between code sections is some variable defined at the top level scope in relation to all code that needs to access it. You can't avoid this.
    The only choice is whether you use only wires to access the variables or distribution/reception busses, or IC send/receive or quick bus etc... Which is best depends on the situation.
    (You can also use multiple instance event tables in primary and OBC in core).
     
    Last edited: Jan 15, 2018
    • Like Like x 1
  6. Johnny-T

    Johnny-T New Member

    Messages:
    16
    A module, that uses - let’s say - the global sampling rate clock, is harder to reuse in the context of subsampling or event processing than a module, that gets the clock through the „front door“ (an direct input wire or bundle). The input could be defaulted though by sr.c if not connected.

    Modules should hide implementation details and should clearly communicate their purpose.
    Regular in- and outputs are helpful. „Backdoor“ dependencies can only be taken into account by looking into those modules.

    Equally connected, identical modules shouldn’t behave different in another context, e.g. where the global sr.c is overridden.

    On the other side, there are dependencies, that are used all over the place and would lead to really crazy wiring.
    It makes sense, to provide those globally. The build-in examples like sr.c do make sense. I only used them as examples.
    But then it would be easier to read, if they are not overrideable.

    And: This is only my opinion, which is vastly influenced by software development far away from dsp programming.

    PS.: Giving PI its name isn’t about providing it once and reusing it multiple. It’s more about the name and it’s meaning to the reader. 3.14 may be obvious, but what’s about 4,2426? It isn’t to cumbersome to define it in every module that uses it. The module wouldn’t work in another context when providing it globally. The module wouldn’t be self consistent any more.
     
  7. colB

    colB NI Product Owner

    Messages:
    3,969
    This isn't c/java/c++, there are no functions here. Only spaghetti and cut'n'paste.
    In c, if you create a function with 4,2426 defined in it, and call it 21 times in a large project, then discover the value is wrong and should be 4,2726, you just edit the function definition, and you're done. Same for classes in c++.
    In Reaktor, you now have to find every cut'n'paste copy and edit it manually. Extremely time consuming and error prone. What if you only find 20, the remaining one could cause many of hours of work due to e.g. intermittent bugs that are hard to replicate...
    But wait, if you set up a global distribution bus with a descriptive name, and attach 4,2426 to it, every cut'n'paste instance will use this, and the value can easily be updated without all that extra work. Much better!
    The only things it makes sense to try and reuse that contain data are old well tested and fully debugged code chunks. Truly reusable code is extremely difficult to write in Reaktor. Even the factory library stuff often needs editing to work properly in certain circumstances. I find it's often easier and quicker to write stuff from scratch rather than trying to debug existing code that should work but doesn't. A nice side effect of this is that global dependencies are not an issue, and maintenance problems due to cut n paste inconsistencies are easier to avoid.
     
  8. mosaic_

    mosaic_ Guest

    The only truly reusable Core macros I find myself making tend to be small math or event processing doodads. If, for example, you have a monophonic Core Cell that analyzes a sample and feeds a dozen data streams into a polyphonic Cell for playback, it's going to be hard to make anything truly reusable on a high level from that. I think after a certain level of complexity, it's better to try and learn from code and understand the methodology it uses instead of expecting to be able to copy-paste it into your structure. The dependencies just get too obscure and complex.
     
  9. herw

    herw NI Product Owner

    Messages:
    6,421
    yes:
    global constants.jpg
     
  10. herw

    herw NI Product Owner

    Messages:
    6,421
    Main point for me is, that i am able to understand the structure after years ! This is only important for very large and time intensive projects.

    Internal quick buses clear the structure
    structure 1.jpg

    I try to use similar structures for different applications so i am very quickly informed about signal flow:

    structure of an oscillator
    structure oscillator.jpg

    structure of an envelope generator
    structure envelope.jpg

    I am using often buses (Max Zagler's multiplexing library from partials framework) and quick buses, here {b} to send information about numbering and adressing macros (i call it here containers).
    structure bus.jpg

    Multiplexing is one of my favoured methods to send many informations (here events from panel elements) from primary to core.
    I am using a combination of multiplexing and bundles and quick buses.
    structure multilexing.jpg

    interesting is the structure of OBC connections which seems to be a star layout network (Quick Bus M) but is really linear:
    structure OBC-connection.jpg

    Although i am deleting and inserting core macros and rearranging the OBC bus the initialization is always from left to right.
    Thanks to Max Zagler and his built macro OBC-value.

    Main point is that you recognise your personal programming style and understand the structure after years - so you get routine.

    The possibilty to give every core and primary macro a pictogram helps too.
     
    Last edited: Jan 16, 2018
    • Informative Informative x 1
  11. herw

    herw NI Product Owner

    Messages:
    6,421
    I have a main rule: primary only for panel elements and display and core for signal processing.
    So the main structure is very simple and always similar for different projects:
    structure 2.jpg
    from left to right:
    • initialization of multiplexing: defining different buses
    • GUI: user interface means panel elements like knobs buttons, displays etc.
    • RAM is here in this project special only for managing the screen wires
    • Kabel displays the wires on screen
    • SVA (Signalverarbeitung / signal Processing in core) here polyphonic and monophonic in a primary macro.
    similar structures in primary and core too:
    structure 3.jpg

    multiplexing is extremely useful when there are many panel elements, f.i. here the sequencer container has 129 panel elements! Do you really want to connect 129 wires from primary to core?
    And this is only one container.
    structure 4.jpg

    Although i have to merge 129 panel elements to a bus i can use similar macros and modules several times f.i. here ind.msg. which defines the addresses (index) and the message of a panel element automatically.
     
    Last edited: Jan 16, 2018
  12. colB

    colB NI Product Owner

    Messages:
    3,969
    Agreed.

    One useful rule of thumb is that if there is one signal input and one signal output and every input event always generates an output event, externalise all parameters and apply them internally using math-mod components and you have a reusable macro. e.g. saturator, filter, waveshaper, amp... etc.
    Still have to catch any inputs or parameter values that could cause issues, and deal with potential problems with initialization - e.g. parameter of zero causing NaN during init...
    As soon as there is conditional signal flow routing, conditional internal event generation, multiple inputs feeding one output... etc. there are potential problems!

    A good example is the humble cross-fade. It seems like it should be a no brainer to build a one size fits all cross fade?
    Three inputs: a, b and x(fade), just wire it up macroize it and forget about the internals for ever more. Job done.
    But wait! sometimes a and b are audio signals and x is an event rate parameter, in this case, events at x should not generate output. On the other hand, maybe there are circumstances where a and b are GUI driven parameters, and x is an audio rate stream, in which case you want only events at x to generate outputs... there are other possible configurations, even in something as simple as a cross fade. If you assume that a 'reusable' library component will 'just work' you end up with glitches and bugs that are difficult to understand and even harder to track down.

    It's still nice to have a cross fade macro, but I always open up and double check the event logic for whatever particular context, so in my final code, many things like cross fades, and even z-1 modules are internally edited for a particular circumstance. If the inputs/outputs haven't changed, I try to remember to change the title and at least remove the factory icon, but when debugging, I will always look into macros anyway, because even the internal code of an un-edited 'correct' library macro can be the source of a problem.

    heh, remember pre R6, there used to be a 'one size fits all' z-1 module? [EDIT: oops forgot the no DNC version so that makes 2] not any more ;)
    For many things it's easier to just build the thing you need at 'bare metal' level directly with OBC modules rather than trying to remember what all the different versions of z-1 and latch do - or rip 'em open and bend them to your will ! At least then you understand what's really going on.
     
  13. Johnny-T

    Johnny-T New Member

    Messages:
    16
    Hard times for software guys like me. I never thought about playing the hippie dreamer role in a thread. :)

    With 4.2426 I meant naming it „3sqrt2“. No one could guess what my number means. Fully named (maybe even a level higher) puts all the information in readable form into the code, that was the reason of writing it.

    Good point. Guilty. I edited it too. Damn.:cool:

    I thought, it could be possible to write core modules like the zdf toolkit. With a nice Integrator sign and so on. Ok, I’m not fully aware of the math behind it, but I can see that modularity played a role.
     
  14. Johnny-T

    Johnny-T New Member

    Messages:
    16
    Nice main structure herw!
     
  15. colB

    colB NI Product Owner

    Messages:
    3,969
    The ZDF toolkit is a special case - it's components are not generally re-useable, most can only sensibly be re-used within the context of the system they are part of. This makes it much easier to avoid the problems I've highlighted. Even so, the ZDF toolkit is still very complex, somewhat confusing, and using it involves a very steep learning curve. It's an amazing piece of engineering though - the way it constructs and solves feedback equation depending on the structure of the filter, and simultaneously processes the result. Very clever.

    It would certainly be possible to create other 'frameworks' where a system of components work together in a carefully defined and restricted manner, but this involves significant levels of dependency - the components are not all general and will not all operate independently in arbitrary contexts.
    ---------

    An integrator module fits into the rule of thumb I suggested in that for every input there is always exactly one output and the parameter is internally applied using a math mod component. The ZDF integrator module contains code that is only relevant to the ZDF toolkit, although it will work outside of this context if you only use the in/out ports and the w/2 parameter input.

    ---------------------------

    From a c/c++ mindset, Reaktor 'macros' don't even have the level of reusability/modularity of c style preprocessor macros !!
    At least c macros are defined in one place, and bugs only need to be fixed in one place.
    The closest analog to Reaktor macros really is code folding in a fancy editor combined with cut'n'paste.

    In mainstream programming languages, the concept of code re-use is about having chunks of code - source or binary - that are accessed by lots of different processes and even by different applications. So you might have static library code that is included into your applications, or you might access dynamic library modules that are part of the operating system... functions of various kinds... preprocessor macros... even the humble subroutine counts.
    If instead you used cut'n'paste to copy a section of source code to lots of different places in the source files of an application, then tried to excuse this to your boss in the inevitable disciplinary meeting by claiming it as 'code reuse', I don't think you'd be working that job much longer!
    But that's all we can do with Reaktor, there is no other way! as a result development strategies that are good practice in other programming languages often make no sense here, or just cause problems.
     
    Last edited: Jan 17, 2018
  16. colB

    colB NI Product Owner

    Messages:
    3,969
    In the spirit of the thread, here are some examples from one of my WIP projects:
    The project is an 80s style poly synth. Some basic things that I would recommend as guidelines (they work well for me):

    # Each code section macro / core cell etc., should fit onto one screen - you should be able to see it all without scrolling. As soon as this isn't possible, it's time to tidy up, and package some of the code into macros. This is a nice flexible way to keep things manageable - a good compromise between too much too look at, and too much macro diving. Obviously this depends on the size of monitor and resolution, and whether you need a split with a lot of space for GUI... but that's ok IMO, just package stuff up more when it needs to fit to a smaller window. Pragmatism WTF.

    # Like Herwig suggested, do the processing in core and the GUI in primary. In this project, I have split the processing into mono and poly core cells to improve efficiency. So there is pre-poly mono section for the LFO, and a post poly mono section for the final VCA(not sure if this is historically accurate:)) and the chorus. The chorus is still external because it was ripped from an older project and has not yet been re-factored and packaged up. When that's done it will look even tidier at the main Primary level.

    # Controls are multiplexed in groups of 8. I've tried systems where all controls (100+) go down 1 wire, and the added complexity of de-muxing just makes this less practical. groups of 8 works really well, is simple so use and easily extendible. It just works. e.g. the vcf controls go down a single wire all the way through the initial core level to the inner VCF macro, where they are demuxed and used.

    Pics:

    Main Primary level
    pic1 primary.PNG

    Control multiplexing
    pic2 ctrl mux.PNG

    top level polyphonic core cell
    pic3 poly section.PNG

    main vcf macro
    pic5 vcf.PNG

    Obviously there are chunks of raw code that could be packaged up here and there, but they are in development, and may change, so doing that before it's necessary isnt' always a good idea, so I wait until it doesn't all fit in the window before doing that busy work :)

    FWIW, I often don't follow these guidelines for various reasons (laziness) but they are a good target.
     
    • Informative Informative x 1
  17. herw

    herw NI Product Owner

    Messages:
    6,421
    laziness is the main argument against ;)
     
  18. Catman Dude

    Catman Dude NI Product Owner

    Messages:
    761
    Doing without a call/return mechanism feels like something of a drag. We expend a lot of breath over iteration and objects, but a simple call/return addition would provide considerable extra functionality to core. The called function itself of course would become reusable core code!
    Paraphrasing the old movie, 'Wall Street', 'Globals are good.' (Well, necessary here.)
     
  19. Catman Dude

    Catman Dude NI Product Owner

    Messages:
    761
    The more I look at your code and diagrams the more attractive various pieces of the partials framework seem to me (not at all necessarily for banks of partials). And it starts to look less like an imposing foreign language and more like useful and usable meta-core programming tools. Thanks!
     
    • Like Like x 1
  20. colB

    colB NI Product Owner

    Messages:
    3,969
    When I say call/return, I mean an equivalent to that. In reaktor it wouldn't look like a c style function. It would basically look just like a macro, but maybe be a different colour and its properties would say which macro it is an alias of, and you could right click it and 'definition structure in other pane' or something. So there is a single template macro with many aliases that echo changes to the original template.

    It is something that has been requested for many years.

    Now that NI have moved towards having a registration system for Blocks to use in Racks, maybe some of the ideas used in that might be applicable here. Ideally a template/alias/funciton/reuse type of thing would have some mechanism to keep track of the original definitions. That way we could create and use libraries.
     
    • Informative Informative x 1