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):
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.