@ddw_music By "straightforward" I don't mean to say that everything's convenient, only that the complications aren't rocket science. I think of it like picking crab meat--potentially annoying, but anyone can do it (and will do it well if they like crab enough). But maybe I'm wrong, gimme examples.
I'll start with ipoke~.c which is really simple because it only consists of loops and branching. ipoke~.c
In section 1 you can see the algorithm processing each sample of the audio and position vector in turn, and I decided to hard-code 64 sample blocks to reduce the number of things that I could screw up. Section 2 is a simple branch based on a test, which you could translate as a [select] or [route] among other ways. To make it run AFAP I think the author avoided function calls in favor of nested if-then-else statements, but to me they're hard to read and understand so I made sections 3 and 4 subpatches, which are like subroutines in that they hide distracting details. (No disrespect to the author implied--the code is beautifully formatted and commented) Finally, if you can find the end of that first if-then-else you will see that there is some post processing for both cases--the current index is copied into the last index in preparation for the next iteration. That's section 5.
Porting loops is just a little complicated by the fact that Pd only has one looping construct with which to make for, while, and do-while loops. Pd is kind of spartan that way.
Moving on to recursion, if you've ever programmed in assembler, you'd know exactly what to do because assembler doesn't have function calls. If you want them you have to make them yourself or write a macro to generate them. The way I learned is to start by pushing the address of the next instruction after the call, then push the function arguments, and then jump to the beginning of the function code. The function code then can pop args off the stack (or just read them in place), and to return it simply jumps to the return address that the caller pushed on the stack and restores the stack to its original state.
Since there's no return mechanism in Pd (@ddw_music pondered that earlier in this forum) we just need to push and pop the arguments at the appropriate points. Here's my port of quicksort: quicksort hoare.pd
First, look at the circled Pd section in the upper right. That's the top call of quicksort(). You can see I'm pushing the low and high indexes of the entire array, and then banging the quicksort graph--that's how you "call" that Pd function with args. Section 1 is the entry into the function--it gets its arguments from the stack and then proceeds with its processing. Phrases 2 and 3 are the "tail recursion" calls, and since their order doesn't matter, I didn't use a [trigger] on the output of [pd partition]. First, the partitioned boundaries are pushed for each recursive call, and then we just let [until] bang the quicksort graph again. When there are no more args on the stack, there are no more recursive calls to process, and so we bang "done" to stop [until]. Rewriting tail recursion as iteration is one of those exercises they give CS undergrads.
If you can guarantee that the recursion is very shallow, then you can simply use message feedback to implement it, but the way I just demonstrated is full-strength; the recursive depth is limited only by the maximum list length.
So in both cases you can see that I'm just creating equivalent graphs in Pd on the right for the procedural textual code on the left. No refactoring, no use of special Pd facilities, no particular creative insight into the algorithms.