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

Core Advice needed - loops

Discussion in 'Building With Reaktor' started by Bääääär, Feb 1, 2012.

  1. Bääääär

    Bääääär New Member

    Messages:
    20
    Hi there,

    I'm currently programming in core to get some applications for the monome working. I have a problem with event loops though:

    I have an audio table which is controlled by an core event cell. The cell's task is, after an event for starting has arrived, to set the right write position and when a number of samples has been written, to stop. Pretty simple task actually.

    I have a counter, that constantly counts upwards (kind of a ramp oscillator). It feeds the event cell. Now in the cell at the end, right before the write position for the audio table is returned to the primary structure, there is a small macro, that checks, if a maximum number of samples has been written. In this case it spits out an event that switches a router somewhere up in the structure to stop the process. This router is upstream, so reaktor tells me there is an event loop. but only the router is switched. There is no loop actually.

    What can I do to solve the problem?
    I attached the critical structure. When an event at "running" is received, it shall start spitting out values from zero to the maximum number (which is inserted into the macro via the "max" input). "Counter" counts upwards all the time until max is reached, then starts again (ramp). The modulo and subtraction are so that it's not relevant where the counter was, when the write operation was started.
    The "End" output is meant to output the last sample that has been written (for playback later) The "Pos" Output is meant to - well output the current position.

    Thanks for any help,
    Bääääär
     

    Attached Files:

  2. colB

    colB NI Product Owner

    Messages:
    3,969
    You've attached a core cell. there are no 'event loops' in core. So what you attached cannot be the 'critical structure'.
    Why not just upload your module? or at least a picture of the primary layer structure where the problem is?

    I would guess that you probably need to reorganise your 'code' somewhat... possibly splitting your core cell into two... but it's not possible to know without seeing a lot more of the structure.
     
  3. James Nicholl

    James Nicholl Forum Member

    Messages:
    354
    You won't be able to make any event loops with a Core Cell in the signal path, even if you're only switching a router. Reaktor just plain won't let you do that, regardless of the logic of your structure.

    You'll probably need to do some of the work outside of Core.

    You may also be able to avoid the event loop by reorganizing your structure. If the router you're switching is part of the same Core Cell then you can side step the event loop, since you can use an OBC to control the Core router upstream.
     
  4. Bääääär

    Bääääär New Member

    Messages:
    20
    Sorry, Event loop was probably the wrong word for it. It's named feedback. I mean that orange "Z" mark, indicating, that Reaktor introduces a one sample delay here. I tried to insert this one sample delay manually (as suggested in the manual) but the "Z" won't disappear. I noticed, that this one-sample delay won't work, if I do not connect the clock input, becourse the internally connected SR.C is not available in Event Cells. So I connected my clock signal to that input, so it should switch off the router when the next incrementation of the clock arrives. That was the theory. Unfortunately it does not work. I attached two images. The first shows my setup. The socond gives you an insight into the z^-1 ndc module. And there, still the Z appears.

    I have this problem on several places within my structure. I sometimes need to switch a state or reset a router from a position downstream. And then there are these orange "z"s and I can't figure out how to get rid of them.

    Thanks a lot for help,
    Bääääär
     
    Last edited: Feb 2, 2012
  5. Bääääär

    Bääääär New Member

    Messages:
    20
    Could not attach images, when editing...
     

    Attached Files:

  6. colB

    colB NI Product Owner

    Messages:
    3,969
    Yes, it is really annoying. Inserting a z-1 manually only kills the autoZ if it's at the same level in the heirarchy. If your manual z-1 is nested, it won't work!

    For your purposes, a rewrite of the code seems like the best option anyway.

    e.g. you are counting at the start, then processing, then doing a test to check if the counter should stop. To pass the response of the stop test back to before the processing, you need a sample delay (z-1). Instead, just have the counter stop itself after the correct number of ticks. That way there is no need for a check after the processing section, and no need for 'feedback' or a z-1.

    For those cases where you really do need a z-1, and are having the problem of the auto orange Z being there, there are two alternatives.
    #1 Put a z-1 in the same module layer to kill the auto Z - this is often pointless as the auto does exactly the same job and is less hassle for you.
    #2 manage the z-1 process yourself by feeding a memory channel through your structure using latch ins/outs. This is more elegant, more flexible, and much tidier. Getting into the habit of doing things this way also improves your understanding of core and opens up your creativity.

    I've attached an example macro that demos the Z problem and the two 'solutions'. Hope this helps.

    cheers

    Col
     

    Attached Files:

    • Like Like x 1
  7. colB

    colB NI Product Owner

    Messages:
    3,969
    Did you use one of the current z-1 modules to make an integer version?
    The z-1 modules in the library seem to use a 'special' macro container that tells the compiler to check for z-1 and not provide an automatic orange Z.
    If you just used a standard macro container (or copy one from an instrument created during the reaktor development before the 'special' container was introduced), the compiler won't recognise is as a z-1, so it will still add an auto Z.
    If you build an integer z-1 by converting a library ndc z-1, then it will work.

    EDIT: oops, misread your last post.
    There is a Z on your clock input because there is a feedback channel from the output of the z-1 back to the clock input with no 1 sample delay - so the compiler must add one. You really need to rethink your structure to deal with this. I can't help unless you upload it.

    The problem is that the z-1 module creates a sample delay between its top input and its output. The clock input drives the output, so there is no delay from that input. If you understand how the z-1 works, you will understand the problem.

    Is there a reason why you don't have the counter in core as well?
    If you did that it would be much easier to dodge the Z
     
    Last edited: Feb 2, 2012
  8. Bääääär

    Bääääär New Member

    Messages:
    20
    Hello again,

    thanks for your good posts!

    I can only explain the reason why there is no counter in core, by giving you an overview over my application:

    I want to have 49 independet sample slots that can be recorded and played back. So I built an instrument with 49 voices that contains some mono stuff and only a few components in poly. These are an audio table to store the sample data and this core event cell that starts recording and playback when desired.
    There is a (mono) clock that runs upwards constantly like a ramp oscillator. This is done in primary, I might transfer it to core some day, but not right now (i don't see the point why it would be better). So this clock feeds the 49 voices. Why not a seperate clock for each voice? To minimize CPU usage per voice.
    I wanted to make as mucha s possible in core, becourse I only need integer calculations and primary always uses floating point numbers. These 49 voices need to be as efficient as possible. From what i can say right now, this idea works out pretty well, i was able to get about 60% less cpu usage with this core stuff compared to the primary version. Unfortunatly my core experience is limited and my core realisation doesn't work so far.

    I used the one that's available via this right-click menu and edited it to work as integer.
    thinking about that, it seems pretty logical that the z is still there. Thanks, that made it easy to understand!

    Right before I read your post, this idea came to my mind, too. I attached a version of my structure that makes use of read/write modules to solve the problem. Unfortunately, the router never switches into the "on" state. Activating debug mode, I see that the read-module always returns "0". Any idea why?

    Thanks so much for your support,
    Bääääär
     

    Attached Files:

  9. colB

    colB NI Product Owner

    Messages:
    3,969
    You only have one write module on that memory channel and it only ever writes a zero. It will also be initialised to zero by the system. So a read from it will always produce a zero.
    ---
    I have re-read you description of what this core module does, and I still don't understand why you have any feedback at all.

    To me it seems that what you need is a start/trigger event, and your 'max' number of steps to do (or is that max Pos index and a separate number of steps ? doesn't change things really).

    You then set up a counter with two outputs, one for Pos, the other for the 'end' event.
    I don't know how your 'end detection' works, but it can't be very complex as it only knows about the Pos value, so if you need to you can incorporate it into the counter as well.
    Doing it this way, there would be no feedback, so not even a single sample (which can complicate things and introduce insidious little bugs).

    It really doesn't make sense to have feedback for this functionality, but as you seem unwilling to post the ensemble, or even an edited version for debugging, I can't help with that.
     
  10. Bääääär

    Bääääär New Member

    Messages:
    20
    Sorry, colB, didn't want to be rude, i just thought, it might make things even more complex, if i post my complete ensemble.

    Ok, I deleted all the unneccessary stuff. There is this one instrument that has 49 voices. There is my mono counter (ramp oscillator from zero to "Max"). And my 49 sample cells (each with it's own record-control and play-control). The huge CPU consumption comes from this Play-Control macro that's meant to manage playback. But it's done in primary so far and eats up 4/5 of the ensembles total CPU consumption. (hence my idea to transfer it all to core). Btw: I'm not even sure if "playControl" works, becourse the recording does not work so far.

    On the control panel there is a slider to select one of the 49 sample slots. Press "aim" and the cell is "prepared" for recording. Play a sound that triggers the gate and the recording led will light up. Form then on it should record, but it doesn't.
    Right now, it's meant to record until the "max" number of samples is reached. In the future, i want to add the possibility to stop recording. Thats why i pass this "end" parameter to the play control, so it knows, where the sample ends.
    Once again: I thought it's a good idea to have one counter for all the cells, so i can save a bit of cpu. Maybe that's wrong, I'm not very experienced so far.


    I hope that makes things a bit clearer for you. Sorry again, I didn't want to annoy you.


    Bääääär

    PS: Regarding xour last post: I have two write modules there. There's one in the very left, that's triggered by the "running" input.
    PPS: I made it only 20 voices large, should make no difference.
    PPPS: Why is the ensemble file 2MB, the zip only 19kB?
     

    Attached Files:

  11. Bääääär

    Bääääär New Member

    Messages:
    20
    The last ensemble was rubbish, sorry, here's the right version.
     

    Attached Files:

  12. colB

    colB NI Product Owner

    Messages:
    3,969
    oops, I didn't notice that one :-o
     
  13. colB

    colB NI Product Owner

    Messages:
    3,969
    The logic you are using for detecting 'end' seems flawed to me. (EDIT: actually its not as simple as that - see the rest of the post)

    You need to set up a counter that starts when the gate opens and stops when it reaches max. That will solve many of your problems. The counter can generate the 'end' signal and pass it forward, so no need for feedback. All the logic for controlling the process would be in one place, separate from the process itself.

    The way things are now, with everything mixed together with multiple tests for the same state in different modules, and feedback (with it's sample delay), it is very easy to introduce bugs, and very difficult to find them.
    e.g. bug #1 the 'aim' light stays lit! but why? You are driving it from the Pos output of the WriteControl module. Unfortunately, when the write module stops running, there is no output from Pos, so the 'aim off' state is never driven to the lamp (driving the status module directly from the counter input fixes this). My point though, is that finding bugs like this is much easier when you separate state logic from processing - so do it :)

    Ok, I had another look at the PosControl module and found:

    bug #2 The input section of the posControl module has an IValue module. My guess is that the intent is to counteract the effect of the continuous counter by catching the value at the gate start and then subtracting that value from the following counter input values. Thereby generating a count that starts at 0 each time ?
    Unfortunately, this fix/hack has a nasty side-effect: when the gate is triggered, the IValue causes the counter value to be subtracted from itself generating a zero output. The gate also starts the write process causing the zero to be passed through the imod to the endDetect module. This tests to see if the current input is less than the previous one (testing for wrap-around) - if it is, it signals 'end'. So in almost every instance, the zero passed in by the IValue/subtract pair causes the detector to signal 'end' instantly!

    Solution? as described above, use a counter that starts when the gate opens, then when it reaches max, sends an 'end' event and stops. then use that to drive the system. most of your problems are gone.
    If you try to fix what you have, it will just become more complicated and you will introduce more bugs.

    Good luck

    Col
     
  14. Bääääär

    Bääääär New Member

    Messages:
    20
    Hi ColB,

    That's right! I didn't think about the side effect with that end detection circuit. Strange is, that there never occured an end detection when i tried my ensemble.. Well, I will completely rebuilt it. Doesn't make sense to track bugs here.

    I think you're right... In the end, a seperate counter for each sample cell is not that much more processing than all the state logic I'm using right now... I don't want to use an audio cell, becourse it only has audio outputs (why btw?) and they produce heavy amounts of cpu usage. On the other hand event cells do not have SR.C connected, so I will have too feed an event cell (containing the counter) with an event from the primary level for each incrementation, right? Is there a better way to do this?
    It's really annoying that audio cells only have audio outputs. I guess it has a reason, but I dont see why it's like that.

    Thanks for your advice, it helped me a lot to understand core a bit better. I will try to rebuilt it with a seperate counter. We'll see, if I can get this running.

    Thanks!
    Bääääär
     
  15. colB

    colB NI Product Owner

    Messages:
    3,969
    It was actually generating end events - I tested that.
    If you just build a single counter and make sure it is within the cell that is after the voice logic, then it won't use any extra cpu, because you are only processing the voice that is currently selected ,so only the counter for that voice will be started.
    Even if you do expand it so that it is multi-timbral, a counter in core is a very efficient process, and you will save more through simplicity than you will lose through repetition.
    Not really. In the grand scheme of things, its a small overhead.

    It is VERY important when programming (in any language) not to optimize until you need to. Often you just don't need to. When you do, it's often not the things you expect that are causing the overhead. Most often its weakness in the design rather than lack of low level coding optimizations.

    some choice quotes nicked from wikipedia:

    "We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified"[5] — Donald Knuth
    "Bottlenecks occur in surprising places, so don't try to second guess and put in a speed hack until you have proven that's where the bottleneck is." — Rob Pike
    "The First Rule of Program Optimization: Don't do it. The Second Rule of Program Optimization (for experts only!): Don't do it yet." — Michael A. Jackson

    What they are saying is, don't optimize unless you have to. If you have to, only do it after the program is completely functional and you have analysed it to find where the real efficiency problems are.

    As far as audio vs event: your core module is working at audio rate - you are driving it with an audio rate counter and it is outputting events at audio rate to drive the tables. There will be little difference between generating the audio rate event signal an primary and sending all those events to core compared with using the SR.C to generate the counter within core and having to convert some signals to event streams at the output. In the end, you should just do whatever is simplest. Then change it later if it proves to be to costly in cpu terms.

    I remember some sort of optimization where you use a dupFilter at the output of a core cell. Might work in this context. But I've never found the need.

    cheers

    Col
     
  16. Bääääär

    Bääääär New Member

    Messages:
    20
    I finally managed to get it all working. I even managed to reduce CPU consumption a lot, so from originally 50% on my maschine it dropped to only 19%! Great! When working with so many voices, even the tiniest things can make a huge difference! Sometimes optimization is worth a look :)

    Well, at the moment it is only possible to record one sound at a time (even though you can aim different slots so they all record at once). In the future, i want to have the possibility to record from different sources at the same time and therefore i need full independence.

    Regarding the optimization thing: I don't fully agree with that. When your app is working, it can be very hard to transfer it all into an optimized form. An Example: I built all of this in primary first and it worked at 50% cpu. Now moving this to core is (obviously) not easy for me. If i would have thought about that earlier, it would have saved me the work on the primary version.
    so thinking at possible bottlenecks when outlining an application is a good idea, even though it must not be taken to the extreme.

    but i have control, over when the cell is creating an event in primary and when not. And one more improtant thing: In my monome-programming i use events not only to transfer values, but mostly i use their pure "event" character to trigger things. When an audio cell spits out audio, then i can't use this event-character, unless I work with edge-detection outside the core cell.

    Anyway, I attached the structure, so you can take a look if you like.
    One thing appears to be strange though: I seems like the Audio table is only Mono! I can record on one voice and play it back. when I switch to another voice, no playback is possible (as expected). No I record on that second voice and play that back. Works fine, too. But when switching back to the first voice and playing that one back, i get the sound of the second voice. (I hope it's not too confusing, sorry for my bad english)

    Thank you so much for all that work you do, supporting me :)
    Bääääär

    PS: I can't upload it to the board... "413 Request Entity Too Large" (the file with its 1,5MB is much smaller than the 10MB max-size for zip).
    You can download it here now: http://www.file-upload.net/download-4086772/sample_Cells.zip.html
     
  17. colB

    colB NI Product Owner

    Messages:
    3,969