I really have no idea about Max, but it seems they have some sort of global transport system now:

https://docs.cycling74.com/max5/refpages/max-ref/transport.html

In Supercollider, TempoClock has some notion of transport, as you can freely change the tempo and current beat, but I doesn't quite work as one would expect from a DAW sequencer.

But as Miller said: don't worry about transport in Pd, it is not part of the infrastructure. After all, there are so many different approaches to sequencing that it might be best to just provide the basic tools and don't enforce a particular idiom.

Christof

On 25.10.2020 16:40, Miller Puckette via Pd-dev wrote:
I don't know of any common approach to 'transports'.  Those are considedred
applications, not infrastructure, in Pd (and I think it's the same in
Max and SC, but I'm not an expert in those).

cheers
M

On Sun, Oct 25, 2020 at 08:35:30AM -0700, Iain Duncan wrote:
Ok cool. Thanks Miller, I will definitely look at the pipe code.

Is there an external that has become the defacto version of a transport or
anything like that? I would like to support common use cases in the most
helpful way possible.

On Sun, Oct 25, 2020 at 8:32 AM Christof Ressi <[email protected]>
wrote:

Hi, Pd doesn't have a notion of global transport time, it just knows about
logical time. People can (and have) build their own transport abstractions
on top of that.

For further inspiration you could look into Supercollider's TempoClock
class. The idea is that each TempoClock can have its own logical time and
tempo. Changing the time or tempo of a TempoClock affects all Routines that
are scheduled on this clock. You can, of course, have more than one
TempoClock at the same time.

Christof
On 25.10.2020 16:16, Iain Duncan wrote:

Thanks Christof, that is helpful again, and also encouraging as it
describes pretty well what I've done so far in the Max version. :-)

I've enabled both one shot clocks (storing them in a hashtable owned by
the external) and periodic timers. The one shot clocks exist in both a
transport aware and transport ignorant format for working with and
quantizing off the Max global transport, and there are periodic timers for
both too. (The transport time stops when the transport is stopped, the
other is just a general X ms timer). I have also ensured the callback
handle gets passed back so that any timer or clock can be cancelled from
Scheme user-space. (is there such a thing as a global transport in PD?)

I was actually planning on something like you described in Scheme space: a
user defined scheduler running off the timers. I will look into priority
queues. I had one thought, which I have not seen much, and was part of the
reason I was asking on here about schedulers. I would like to ensure the
user can run multiple transports at once, and hop around in time without
glitches. I was thinking that instead of using just a priority queue, I
would do something like a two stage structure, perhaps with a hashmap or
some other fast-read-anywhere structure with entries representing a time
period, and holding priority queues for each period. This would be to
enable the system to seek instantly to the bar (say) and iterate through
the queue/list for that bar. Wondering if anyone has used or seen this type
of pattern or has suggestions? Basically I want to make sure random access
in time will work ok even if the number of events in the schedule is very
high, thus allowing us to blur the lines between a scheduler and a full
blown sequencer engine. Thoughts, suggestions, and warnings are all welcome.

iain

On Sun, Oct 25, 2020 at 4:21 AM Christof Ressi <[email protected]>
wrote:

Actually, there is no need to use a clock for every scheduled LISP
function. You can also maintain a seperate scheduler, which is just a
priority queue for callback functions. In C++, you could use a
std::map<double, callback_type>. "double" is the desired (future) system
time, which you can get with "clock_getsystimeafter".

Then you create a *single* clock in the setup function *) with a tick
method that reschedules itself periodically (e.g. clock_delay(x, 1) ). In
the tick method, you get the current logical time with
"clock_getlogicaltime", walk over the priority queue and dispatch + remove
all items which have a time equal or lower. You have to be careful about
possible recursion, though, because calling a scheduled LISP function might
itself schedule another function. In the case of std::map, however, it is
safe, because insertion doesn't invalidate iterators.

Some more ideas:

Personally, I like to have both one-shot functions and repeated
functions, being able to change the time/interval and also cancel them. For
this, it is useful that the API returns some kind of identifier for each
callback (e.g. an integer ID). This is what Javascript does with
"setTimeout"/"clearTimeout" and "setInterval"/"clearInterval". I use a very
similar system for the Lua scripting API of my 2D game engine, but I also
have "resetTimeout" and "resetInterval" functions.

On the other hand, you could also have a look at the scheduling API of
the Supercollider, which is a bit different: if a routine yields a number
N, it means that the routine will be scheduled again after N seconds.

Generally, having periodic timers is very convenient in a musical
environment :-)

Christof

*) Don't just store the clock in a global variable, because Pd can have
several instances. Instead, put the clock in a struct which you allocate in
the setup function. The clock gets this struct as the owner.

typedef struct _myscheduler { t_clock *clock; } t_myscheduler; // this
would also be a good place to store the priority queue

t_scheduler *x = getbytes(sizeof(t_myscheduler));

t_clock *clock = clock_new(x, (t_method)myscheduler_tick);

x->clock = clock;
On 25.10.2020 02:02, Iain Duncan wrote:

Thanks Christof, that's very helpful.

iain

On Sat, Oct 24, 2020 at 5:53 PM Christof Ressi <[email protected]>
wrote:

But if you're still worried, creating a pool of objects of the same size
is actually quite easy, just use a
https://urldefense.com/v3/__https://en.wikipedia.org/wiki/Free_list__;!!Mih3wA!U2vSBqdBcvf_RHuxgQObMJTTPs25qVYaFIbydhQbq5R9QW16pv41XKgiaZ9c$
 .

Christof
On 25.10.2020 02:45, Christof Ressi wrote:

A) Am I right, both about being bad, and about clock pre-allocation and
pooling being a decent solution?
B) Does anyone have tips on how one should implement and use said clock
pool?

ad A), basically yes, but in Pd you can get away with it. Pd's scheduler
doesn't run in the actual audio callback (unless you run Pd in "callback"
mode) and is more tolerant towards operations that are not exactly realtime
friendly (e.g. memory allocation, file IO, firing lots of messages, etc.).
The audio callback and scheduler thread exchange audio samples via a
lockfree ringbuffer. The "delay" parameter actually sets the size of this
ringbuffer, and a larger size allows for larger CPU spikes.

In practice, allocating a small struct is pretty fast even with the
standard memory allocator, so in the case of Pd it's nothing to worry
about. In Pd land, external authors don't really care too much about
realtime safety, simply because Pd itself doesn't either.

---

Now, in SuperCollider things are different. Scsynth and Supernova are
quite strict regarding realtime safety because DSP runs in the audio
callback. In fact, they use a special realtime allocator in case a plugin
needs to allocate memory in the audio thread. Supercollider also has a
seperate non-realtime thread where you would execute asynchronous commands,
like loading a soundfile into a buffer.

Finally, all sequencing and scheduling runs in a different program
(sclang). Sclang sends OSC bundles to scsynth, with timestamps in the near
future. Conceptually, this is a bit similar to Pd's ringbuffer scheduler,
with the difference that DSP itself never blocks. If Sclang blocks, OSC
messages will simply arrive late at the Server.

Christof
On 25.10.2020 02:10, Iain Duncan wrote:

Hi folks, I'm working on an external for Max and PD embedding the S7
scheme interpreter. It's mostly intended to do things at event level, (algo
comp, etc) so I have been somewhat lazy around real time issues so far. But
I'd like to make sure it's as robust as it can be, and can be used for as
much as possible. Right now, I'm pretty sure I'm being a bad
real-time-coder. When the user wants to delay a function call, ie  (delay
100 foo-fun), I'm doing the following:

- callable foo-fun gets registered in a scheme hashtable with a gensym
unique handle
- C function gets called with the handle
- C code makes a clock, storing it in a hashtable (in C) by the handle,
and passing it a struct (I call it the "clock callback info struct") with
the references it needs for it's callback
- when the clock callback fires, it gets passed a void pointer to the
clock-callback-info-struct, uses it to get the cb handle and the ref to the
external (because the callback only gets one arg), calls back into Scheme
with said handle
- Scheme gets the callback out of it's registry and executes the stashed
function

This is working well, but.... I am both allocating and deallocating
memory in those functions: for the clock, and for the info struct I use to
pass around the reference to the external and the handle. Given that I want
to be treating this code as high priority, and having it execute as
timing-accurate as possible, I assume I should not be allocating and
freeing in those functions, because I could get blocked on the memory
calls, correct? I think I should probably have a pre-allocated pool of
clocks and their associated info structs so that when a delay call comes
in, we get one from the pool, and only do memory management if the pool is
empty. (and allow the user to set some reasonable config value of the clock
pool). I'm thinking RAM is cheap, clocks are small, people aren't likely to
have more than 1000 delay functions running concurrently or something at
once, and they can be allocated from the init routine.

My questions:
A) Am I right, both about being bad, and about clock pre-allocation and
pooling being a decent solution?
B) Does anyone have tips on how one should implement and use said clock
pool?

I suppose I should probably also be ensuring the Scheme hash-table
doesn't do any unplanned allocation too, but I can bug folks on the S7
mailing list for that one...

Thanks!
iain

_______________________________________________
Pd-dev mailing 
[email protected]https://lists.puredata.info/listinfo/pd-dev


_______________________________________________
Pd-dev mailing 
[email protected]https://lists.puredata.info/listinfo/pd-dev

_______________________________________________
Pd-dev mailing list
[email protected]
https://urldefense.com/v3/__https://lists.puredata.info/listinfo/pd-dev__;!!Mih3wA!U2vSBqdBcvf_RHuxgQObMJTTPs25qVYaFIbydhQbq5R9QW16pv41XF911flN$

_______________________________________________
Pd-dev mailing list
[email protected]
https://urldefense.com/v3/__https://lists.puredata.info/listinfo/pd-dev__;!!Mih3wA!U2vSBqdBcvf_RHuxgQObMJTTPs25qVYaFIbydhQbq5R9QW16pv41XF911flN$

_______________________________________________
Pd-dev mailing list
[email protected]
https://urldefense.com/v3/__https://lists.puredata.info/listinfo/pd-dev__;!!Mih3wA!U2vSBqdBcvf_RHuxgQObMJTTPs25qVYaFIbydhQbq5R9QW16pv41XF911flN$

_______________________________________________
Pd-dev mailing list
[email protected]
https://urldefense.com/v3/__https://lists.puredata.info/listinfo/pd-dev__;!!Mih3wA!U2vSBqdBcvf_RHuxgQObMJTTPs25qVYaFIbydhQbq5R9QW16pv41XF911flN$



_______________________________________________
Pd-dev mailing list
[email protected]
https://lists.puredata.info/listinfo/pd-dev



_______________________________________________
Pd-dev mailing list
[email protected]
https://lists.puredata.info/listinfo/pd-dev

Reply via email to