Drawing lines from a few points?

Discussion in 'Building With Reaktor' started by KoaN, May 9, 2021.

  1. KoaN

    KoaN NI Product Owner

    Hey guys,
    I have a small step sequencer here (from Kodiak) and i'd like to use these steps to output a ramp type of signal.
    I feel it might be more complicated than i thought...and not sure i can do this but better to ask first.
    I tried to find something in the library that could help but didn't see find anything.
    I guess that could be related to waveshaping,from a few points creating lines in between.
    How would i go about on creating this?
    Here's some pics to show what i mean.
    That is my step sequence...
    And i would like to output a ramp from the active steps like this...
    Well or even more simple like this....only 1 ramp for each point.
    Last edited: May 9, 2021
  2. Paule

    Paule NI Product Owner

    How do you get the sequencer to skip the zero steps?
  3. KoaN

    KoaN NI Product Owner

    It's more about creating a ramp down from each active steps
  4. KoaN

    KoaN NI Product Owner

    I guess i could trigger a ramp on each active step....so i would need to calculate the distance,time between each steps to adjust the ramp frequency accordingly
    I will try to read that table and calculate these time measures and then write these in a new array
    A bit harder for me to do that in core without iteration...let's see if i can!
    Last edited: May 10, 2021
  5. Michael O'Hagan

    Michael O'Hagan NI Product Owner

    I made a step sequencer that does this exact thing, here...


    Copy and paste the modulation sequencers and attach them to a step clock.

    The trick is that it's controlled by the smoothing knob.

    Positive smoothing smoothes both up and down, negative smoothing values will smooth on release to zero only.

    Like this...

    Positive Smoothing...

    Screen Shot 2021-05-10 at 1.00.55 AM.png

    Negative Smoothing...

    Screen Shot 2021-05-10 at 1.00.30 AM.png

    Extracting the macro could be a bit tricky because I used a lot of send and receives in this build, but you should be able to figure it out.

    Hope this helps.
    • Like Like x 1
  6. KoaN

    KoaN NI Product Owner

    Hmm but from the pics here i see all your steps have the same smoothing rate applied to each steps...in my last example above you can see the lines are at different length depending on how many off steps in between...First step has shorter ramp compared to the second one.
    So that's the tricky part...i need to calculate the distance in between each active steps and convert that to ramp time.
    I think i can do that in primary but struggling a bit to do that all in core.
    I struggle a little on how to create priorities in core...in primary it's quite easy with the order module.
  7. Paule

    Paule NI Product Owner

    Thank you Michael.
    There are diferent values possible
  8. colB

    colB NI Product Owner

    Iteration of some form is going to be essential in this situation I think.
    Some options are:
    pipe in from a basic iterator
    use partials framework iteration library
    create an unrolled loop with an 'early exit' criterion

    Assuming this is not something that will be modulated, I think the best approach is to do all the processing every time a GUI edit is made. This way it's possible to ensure that all the processing is done on the GUI thread, so the overhead of piping in iteration events from Primary is really not an issue.

    It's pretty complicated, but totally possible. probably the hardest part of doing this sort of thing in core is what to do with snapshots.
    Either they just store the values in which case, you need a separate initialisation process to scan the whole thing and update it, or you need to store the processed data in a snap value array and reload it on snapshot load.

    If the process is fast enough that scanning through the whole thing is not an issue, then its maybe simpler just to do that each time there is an edit - just rescan all events and update all slope values - assuming you would be doing only as a result of GUI events, it should be fine to do this.

    If it's not efficient enough to just rescan the whole thing each time (say you had a long scrollable modulation sequence with thousands of possible entries), then it gets more complex. In this case, each time an edit it made, there are a few things to do:

    If the edit is changing from no entry to a new entry:
    step backwards to the previous entry (assuming there is one!)
    recalculate the previous entry in the context of this new entry
    step forwards to the next entry (assuming there is one)
    calculate slope value to that next entry.​
    those 'assuming there is one' situations will need extra logic to handle them.

    If the edit it changing from an existing entry to no entry, then step back to the previous and process forward from there.

    if the edit is changing an existing value, then step back to the previous and process that, then process from the newly changed event.

    This 'process' is I think:
    keep stepping forward until a value is found that is greater than the previous value - this is a new 'attack' so this is the end point for our current slope.

    This would also mean the when looking back to update a 'previous' slop in the light of a new edit, we have to keep going back until we find an active step that is not greater than the one after it...​

    Totally doable.
  9. KoaN

    KoaN NI Product Owner

    Getting a bit closer i think.
    Shouldn't be very hard as i am just adding another functionality to the sequencer lane "Kodiak" which already does the whole saving of the GUI elements and updating....so i just need to add in another array of info for the slope time following that main sequence.
    I can probably even use the iteration from there instead of adding a new one.
    The sequence is 48 steps only.
    So what i am doing is scanning through the sequence and counting the number of zero steps in between each active steps to give me a reference for the slope time and i will save these in an array and then use the corresponding data for each active steps that will trigger the ramp.
    This will be done on GUI update yes.

    On a different subject i have a couple of questions.
    In primary you use the order module to create priorities...the way to do that in core would be with latching and latching-1 right? This way you can make a process get delayed for 1 tick etc. My question is a bit general but was wondering if it's a common way of doing things in core.

    And about iteration,wouldn't using a counter in core be the same thing as iteration? Obviously it's not that simple but i was wondering why.
    Is it because iteration has its own clock speed?
  10. colB

    colB NI Product Owner

    I think the simples approach would be a 48 part unrolled loop in core.
    The reasoning is that if you go the easiest route and just re-process the whole thing each time there is an edit - to avoid handling loads of bug friendly corner cases - then you need to traverse all steps every time. It's very simple that way, no primary iteration, no 'break' tests in the unrolled loop...
    Really depends in what 'things' you mean!
    Latches are maybe the most common macros in core, latch-1 less so, but still useful.
    In Primary you use the order module because otherwise your code is ambiguous. That's not an issue in core in the same way.
    An order module wouldn't make sense in core because events with the same source are functionally simultaneous.

    Iteration does all it's processing as fast as possible with a single event trigger - it's not clock based (unless you use that speed limiting feature, but I've never found a practical use for that). Counters on the other hand count once per trigger event. So e.g. iteration would let you process a loop say 20 (or 73 or whatever...) times per single clock event. A counter can only ever process one thing per clock event.
  11. KoaN

    KoaN NI Product Owner

    What i have started using the iteration should work well i think,just a few things more to do and i am done.
    I honestly have no idea what an unrolled loop is though...i could check this out,is there a ensemble somewhere with a small example?

    Yah my question is a bit too general,i asked that because i was trying to translate a primary structure into core....i guess you have to think differently once in core.

    Ok i see.
    The speed limiting feature is useful for me when i want to see what is happening,searching for a problem in my structure.
  12. colB

    colB NI Product Owner

    Unrolled loop is very simple,

    10 let a = 0
    20 print a
    30 let a = a + 1
    40 if a=20, stop
    50 goto 20

    So this pseudo code would loop run 20 time printing the numbers 0 to 19.
    Un unrolled version might look like:

    10 let a = 0
    20 print a
    30 let a=a+1
    40 print a
    50 let a=a+1
    60 print a
    370 let a=a+1
    380 print a

    or something like that, with all 20 'loops' explicitly written out or 'unrolled'
    Back in days of yore, this was a technique to optimise processes that had to be really fast. these days it's less obvious when to use this type of approach, but it can be used in Reaktor to get around the lack of iteration in core.

    There's an example of a fairly complex unrolled loop in my convolution macro which is in my convolution reverb, amp sim, and in chet singers serenade:
    unrolled loop2.PNG

    You don't need to understand the process, just notice that there are running sums, and index values that are updated, to that if lots of this macro are strung together it behaves the same as if it were in a loop, like a for loop or a while loop
    Here's another example from the factory 'Grain Sampler' macro:
    unrolled loop 3.PNG
    Interesting trick. I guess you could even replace the iterator with a stepper, so you can hit a button to manually step through...
  13. KoaN

    KoaN NI Product Owner

    Ok so it's like adding in the loops yourself,no instruction to loop back.
    Thanks i will check that out.
    Edit:I think i get the logic,so you only need to send 1 event for example and it goes through all the indexes because of the +1,cool trick!
    Last edited: May 11, 2021
    • Like Like x 1
  14. Studiowaves

    Studiowaves NI Product Owner

    Seems like the simple slew limiter is what your after, Do you need something that self adjusts to give a linear slew between the pulses regardless of their values? If so simply detect the difference between the two pulses and apply it to the speed of the slew limiter. It's probably basic math. There are also other non linear slew limiters that start fast and slow down before reaching the next level. Have fun Koan