1

I'm learning Pure Data, specifically, I'm using puredata vanilla 0.46.7. I'm doing fine with the basics: creation and manipulation of audio signals, controls and messaging, using abstractions, etc.

I had the idea to have a vertical slider inside the main patch that goes from 0 to 1000 so that I can fine-tune it without pressing Shift. However, I would like to give it a specific range, like -10 to 10, and I could do this by simply setting the range of the slider.

I didn't immediately think about it, so I tried this approach: I'll have the outlet of the slider connected to the inlet of range arg1 arg2, which it will be an abstraction, so that by creating for example range -10 10 the range of the slider (0..1000) will be mapped to the range provided as arguments to the object (-10..10).

So, I created this patch (here is the pastebin link of it): range patch

I thought it was fairly easy, and I expected to be successful, but with no avail. Instead of something that goes in the range of -10..10, I get -10..0 as a range instead. As I stated earlier, I could simply set the range directly on the slider, but the fact that this is not working means that I'm missing something, so I would like to know where is the fault in my reasoning, in order to better understand how the control level of Pure Data works.

Based on what I learned about hot and cold inlets, by connecting [loadbang] to [t b b], [f $2] and then [f $1], what should happen is that when I create a new [range] (for example range -10 10), [f $2] is banged, which causes the object to output the second argument passed to [range] ($2). Since the outlet of the object is connected to the cold inlets of the two [expr], it just sits there, and the [expr] objects should output their default value (which is 0).

Then, [t b b] should bang [f $1]: this causes the object to output the first argument. Since its outlet is connected to the hot inlets of the 2 [expr] objects, this should cause the two objects to output the result of their expressions.

They check if $1 and $2 are both 0, which means that no arguments were provided for [range]. If this is the case, the first [expr] outputs a 0 (a default minimum value), the second one outputs a 1 (a default maximum value). Otherwise, they simply output whatever is needed: the first [expr] outputs $f1 (which is the minimum value, stored in [f $1]); the second [expr] outputs $f2 (the max value, stored in [f $2].

The outlets of the [expr] object are connected to the inlets of [swap], its outlets are then connected to [- ]. So, I've passed -10 and 10 as arguments, the minimum value is -10, the maximum value is 10, they got swapped and then subtracted, which means that [- ] should now output 20 (10 - (-10)): this is the range on which I want to map 0..1000.

The [inlet] of range is the value coming from the slider: I divide it by 1000 so that it sits in the range 0..1. Let's call this number n. Now, I want to map n to -10..10, so I came up with this formula: x = R * n + m, where R is the range previously calculated and m is the minimum value, which acts as an offset. So, if 500 is the slider value, n = 0.5. For R = 20 and m = -10, this means that x = 0.0, which is expected: 500 sits in the middle of 0..1000 and 0.0 sits in the middle of -10..10.

This is exactly what the last [expr] object does: $f1 is the value from the slider, $f2 is R (the range) and $f3 is m (the minimum, aka the offset).

By now, given how the previous objects are connected, I should have the right values for $f2 and $f3. If the value of the slider changes, I should see the corresponding output, but it's wrong: instead of mapping onto -10..10, it maps onto -10..0.

Since I can't debug like in a "classic" programming language (that is, go step by step through the code execution), I connected [print] objects to the relevant outputs, to check the order of the operations. I then created [range -10 10] on the main patch, and read the console to see the prints. I got this "trace":

arg2: 10
swap-dx: -10
range: 10
swap-sx: 0
check-min: -10
check-max: 10
arg1: -10

This is confusing, given the reasoning above about hot/cold inlets and how I connected the various objects I would expect something like:

arg2
arg1
check-min
check-max
swap-sx
swap-dx
range

I can't make any sense of the trace obtained: how is it possible that swap-dx is right after arg2? And how is it possible that arg1 is at the very bottom? I understand that messages are passed "right to left" regarding the inlets and "depth first" regarding the layout of the objects, but in this case, to me, it seems that there is no path from arg2 to swap, since the outlets of [f $2] are connected to cold inlets and the respective objects shouldn't output anything until something changes on their hot inlets.

If I saw something like arg2 -> check-min -> swap-dx -> range and then all the rest, that would be a clear indication of the "depth-first" rule. Instead, I got all the above.

However, the result (the fact that the range goes from -10 to 0) is coherent with what I see in the trace: swap-sx: 0, swap-dx: -10, range: 10, and that gives a clue about the problem. For some reason, the hot inlet of [swap] never gets updated and stays at 0, so [- ] makes the subtraction 0 - (-10), which is equal to 10. Hence, the value must be mapped onto -10..0.

I tested each "stage" individually, making sure that all the objects behave as I expected, and they do: the expressions seem correct, there are no quirks with [- ]: if I pass -10 to its cold inlet and 10 to its hot inlet, the output is 20, which is correct.

So, it's clear that all of this is ..unclear to me: what am I getting wrong? What could I do to debug the patch more effectively, aside from putting [print] objects everywhere? Should I use [float] objects to store the values at each stage, leveraging the fact that they output only when they are banged on the hot inlet, and then use a trigger to bang them in the desired order? To me, it seems like overkill and a headache-fest.

Max N
  • 1,134
  • 11
  • 23
Russell Teapot
  • 511
  • 1
  • 6
  • 19
  • 3
    I just took a very quick look at your patch and the potential issue is that you don't have a trigger after [f $1]. You are feeding its result to both hot inlets of two different [expr]. If the data is reaching the top one first, it will reach [swap] in a hot inlet before the second [expr] outputs its value. So in that scenario, [swap] is effectively swapping the current value of one expression with the old value of the other one. Try using a [t f f] after [f $1]; as a matter of fact, always use triggers when a value is being sent to multiple locations that will be used together at some point. – gilbertohasnofb Feb 09 '20 at 10:26
  • 1
    Is there a specific reason why you are using such an olde version of Pd? We're currently at 0.50 – Max N Feb 09 '20 at 11:20
  • 2
    Your patch has fanning output connections which is bound to give you problems. Use a [trigger float float float] (or short [t f f f]) object after [f $1] and [f $2]. Only that way execution order can be visible in the patch. If you're not using trigger, the message flow depends on which connection was made first. – Max N Feb 09 '20 at 11:26
  • 1
    I'm still unsure what you want to achieve, but why are you setting the range of the slider if you can have a normalized slider from 0 to 1 and then just scale the output by multiplication and offset to get the range you need. Wouldn't that be much easier? – Max N Feb 09 '20 at 11:28
  • @MaxN Dumb decision: since I'm dividing the number by 1000 anyway, it's definitely better to start with a slider set to 0..1,as you stated. Regarding the old version of puredata, is the only available package for my machine (Linux Mint 18 32 bit), so it was the quickiest solution for me – Russell Teapot Feb 09 '20 at 15:29
  • so the question should read: why are you using "Linux Mint 18"? it's based on *Ubuntu 16.04*, which is quite old by now... – umläute Feb 13 '20 at 15:50

2 Answers2

1

It still looks overly complicated and not really like Pd native syntax. Isn't this what you are looking for?

Pure Data abstraction to scale a range

Max N
  • 1,134
  • 11
  • 23
  • 1
    Yes, this solution is much cleaner! But I was mostly concerned on the order of operations in a general sense, beside the specific case I shown in my patch. – Russell Teapot Feb 13 '20 at 15:49
  • Edited because two objects were superfluous. Even slightly simpler now. – Max N Mar 01 '20 at 19:22
0

Thanks to gilbertohasnofb and Max N, I figured out the solution: range patch fixed

By adding [t f f f] after [f $1] I can ensure the correct order: first the value goes to the second [expr], then to the first, which causes [swap] to output, and finally to the last [expr].

Now the patch works as expected with no problems! I completely forgot that [trigger] can distribute any type of message, not only bang. Lesson learned: always use trigger when connecting an output to multiple hot inlets.

Russell Teapot
  • 511
  • 1
  • 6
  • 19
  • 2
    Even though it's not necessary, I'd still add a [t f f] after [f $2] just for coding style. Seeing a fanned out connection makes my alarm go off. – Max N Feb 09 '20 at 16:06
  • @MaxN good point, I should get into the habit to do that.. Even with a simple patch like this, I can see the havoc brought by fanned out connections – Russell Teapot Feb 09 '20 at 16:09
  • 2
    since Pd-0.49 (or so), you can select all objects with fan-out (or just do *Ctrl+a* to select *all* objects), and do **Ctrl+t** to insert triggers without changing the functionality) – umläute Feb 13 '20 at 15:47
  • 1
    @umläute well, time to figure out how to install the newest version of puredata – Russell Teapot Feb 13 '20 at 15:52
  • you need at least Pd-0.49 (which i think first appeared in Ubuntu 18.10 or 19.04, and there's no Linux Mint for somethig *that* new), and of course Pd-0.50 is even better... – umläute Feb 13 '20 at 16:01
  • @umläute I managed to build `pd 0.50` directly from source :) – Russell Teapot Feb 13 '20 at 16:51