sub-patch with arguments?
@rph-r Ok, I will not tell you to use an abstraction.
A sub-patch is simply a window within a patch, and it takes its arguments from the patch that it is in.
So if you create [mypatch 1 2 3 4] then inside the patch, and also inside the sub-patch, $1 $2 $3 and $4 will be assigned the values 1 2 3 and 4 as the patch is opened.
If you need to put objects that are identical, but behave differently depending on arguments, within sub-patches then you need to give the main patch arguments.
A sub-patch cannot have its own arguments as it is actually an integral part of the main patch.
To achieve what you wish (I think I have understood) you will simply need to put your main patch within another patch (a container) and give your main patch the arguments.
If you then need to see and use guis that are in your main patch when you open the "container" patch then you can use the "graph on parent" from your main patch properties pop-up to show them in the container patch.
Or you can just open your main patch after opening the container patch.
But abstractions definitely make life easier, especially if you want to modify the current sub-patches for any reason.
David.
Max style Key Bindings
In pd_bindings.tcl, I replaced this :
# and the Pd window is in a class to itself
bind all <KeyPress> {::pd_bindings::sendkey %W 1 %K %A 0 %k}
bind all <KeyRelease> {::pd_bindings::sendkey %W 0 %K %A 0 %k}
bind all <Shift-KeyPress> {::pd_bindings::sendkey %W 1 %K %A 1 %k}
bind all <Shift-KeyRelease> {::pd_bindings::sendkey %W 0 %K %A 1 %k}```
with:
Max style bindings
bind all <KeyPress> {
set w [winfo toplevel %W]
if {[winfo class $w] eq "PatchWindow" && $::editmode($w) && !$::editingtext($w)} {
switch -- %K {
"n" {menu_send_float %W obj 0}
"m" {menu_send_float %W msg 0}
"i" {menu_send_float %W floatatom 0}
"l" {menu_send_float %W listbox 0}
"c" {menu_send_float %W text 0}
"b" {menu_send %W bng}
"f" {menu_send %W numbox}
"v" {menu_send %W vslider}
"h" {menu_send %W hslider}
"r" {menu_send %W hradio}
"d" {menu_send %W vradio}
"g" {menu_send %W graph}
"a" {menu_send %W menuarray}
"u" {menu_send %W vumeter}
default {::pd_bindings::sendkey %W 1 %K %A 0 %k}
}
} else {
::pd_bindings::sendkey %W 1 %K %A 0 %k
}
}
bind all <KeyRelease> {::pd_bindings::sendkey %W 0 %K %A 0 %k}
bind all <Shift-KeyPress> {::pd_bindings::sendkey %W 1 %K %A 1 %k}
bind all <Shift-KeyRelease> {::pd_bindings::sendkey %W 0 %K %A 1 %k}```
Are there any implications? Why isn't this (or similar) a default setting?
Hello world external written in Zig
I recently heard about the Zig programming language and wanted to see if I could make Pd externals with it, so I put together a "hello world" external, which showcases things like printing to console, working with symbols, and sending floats through an outlet.
Getting it to work required using a local copy of m_pd.h
and making a small change to the t_text
struct, since Zig's C-translator apparently doesn't like bit fields.:
- unsigned int te_type:2; /* from defs below */
+ unsigned char te_type; /* from defs below */
Currently, the build file is catered to a Linux installation, but probably all that would need to be changed for a different platform is the system include path and the library extension.
To do a build, just run:
zig build -Doptimize=ReleaseFast
EDIT: Changed the struct to an extern struct
to guarantee the ordering of its fields.
EDIT2: Functions are now inside the struct. This seems to be more idiomatic to Zig, plus it looks nicer.
EDIT3: Link with libc during the build process.
This keeps the external's size small, even when adding math functions like log
or exp
to it. Otherwise, there's an increase of about 115KB because those functions get baked right into the external.
EDIT4: Replace m_pd.h
with custom definitions that allow many functions to be called as methods. Some examples:
adding a method to a class:
pd.class_addbang(myclass, @ptrCast(&bang)); // old way
myclass.addBang(@ptrCast(&bang)); // new way
creating a new instance of an object
// old way
const self: *Self = @ptrCast(pd.pd_new(myclass));
_ = pd.outlet_new(&self.obj, &pd.s_float);
self.sym = pd.gensym("world");
// new way
const self: *Self = @ptrCast(myclass.new());
_ = self.obj.outlet(pd.s.float);
self.sym = pd.symbol("world");
sending a float through the main outlet:
pd.outlet_float(self.obj.te_outlet, f * 2); // old way
self.obj.out.float(f * 2); // new way
repo: https://github.com/myQwil/pd-zig-external
More examples can be found on my library's Zig rewrite branch:
https://github.com/myQwil/pd-quilt/tree/zig-rewrite
COMPUTATIONAL INTENSITY OF PD
@4poksy 64 samples cannot be modified for links between patches and sub-patches.... [inlet~] etc. ...nor for final input and output.... [dac~] etc.
It can be modified within a patch or sub patch using [block~].
Patches are very small on disk and in memory..... your os will tell you the size.... they just contain text.
The biggest patch (hundreds of patches running together) that I have ever built uses less than 100K on disk. The objects you call are loaded to and run within the Pd binary as you open the patch.
Pd itself uses about 8.5K of ram with no patches open, and the gui program "wish" just over 15K (in windows 7).
When that massive 100K patch is running Pd expands to use 76K of ram and wish reduces its space to use 12K.
Arrays are the size you choose, and can be large if you are storing audio..
Pd can (sort of) report its cpu load....... dsp~.zip
For more on what actually happens "under the hood"..... http://puredata.info/docs/manuals/pd/x2.htm
.... but that document is also in your Pd installation in the "doc" folder........ /doc/1.manual/x2.htm
Pd builds a "super-patch" from all the patches you open, and the audio thread is rebuilt as you change or add objects and patches. That means that it is always running, but there is no buffer between patches unless you make a mistake..... see the doc above...
You can also use [block~] or [switch~] to turn off audio processing within individual patches and sub-patches...... useful for saving cpu load when switching between effects.
Welcome to the forum...!!
David.
P.S I am usually wrong about something and others will tell you where.
Where does latency come from in Pure Data?
TL;DR:
"Blocksize": hardware buffer size
"Delay": extra latency
Then, in the Pd audiosettings, you set the "Delay (msecs)". This sets the size of the buffer between Pd and the soundcard.
This is not true. (I don't blame anyone for making this mistake, as this is not obvious unless you know the Pd internals.)
By default, Pd uses the so-called "polling scheduler". The message system and DSP run in a dedicated thread which communicates with the actual audio callback via a (lock-free) ring buffer. "Delay" effectively controls the size of that buffer. It adds extra latency on top of the hardware buffer size.
The actual hardware buffer size is always controlled by the - confusingly named - "Blocksize" parameter. (Pd's internal blocksize is fixed at 64 samples.)
Alternatively, Pd may use the so-called "callback scheduler", which is enabled by the "Callbacks" option in the audio settings. In this case, the message system and DSP run directly in the audio callback and latency is only affected by "Blocksize". (This option has disappeared in Pd 0.54, but will likely reappear in the future after some scheduler bugfixes have been applied. See https://github.com/pure-data/pure-data/pull/1756.)
Tip: If you run Pd with the default "polling scheduler", then you should set "Blocksize" as low as possible and control latency with the "Delay", as it offers finer granularity.
DSP loop with buffer abstraction
The feedback does not occur between [delwrite~]
and [delread~]
if not patched explicitly. [delread~]
reads from the corresponding [delwrite~]
. To create feedback, you'll still need to use [s~]
/[r~]
etc, to send the output of [delread~]
back to the input of [delwrite~]
Mind though that it's best to put the writing and the reading parts of your delay in separate subpatches, where the one with [delwrite~]
will have a dummy [outlet~]
, and the one with [delread~]
will have a dummy [inlet~]
. Then you must connect the two in the parent patch, to circumvent the issue of not being able to drop the delay time lower than one sample block. By making this connection you explicitly force Pd to first compute [delwrite~]
and then [delread~]
, so delays up to a single sample are possible. This is somewhat related to the feedback issue you were having, and the way Pd treats signals.
can pure data vanilla create a date-based folder to store txt + wav files into?
@esaruoho You seem to already have the bang for the messages sent to [soundfiler] as @lacuna suggests..... but you are not sticking the parts of your symbol together as shown in his and @oid's examples.
So have the files been written to the /plom directory instead of the /plom/2023_04_04 directoryintended?
You are breaking up your messages.
A space between two parts of a message will create 2 separate messages.
If you need one symbol you do not need [makefilename].
You need to stick the message dollar variables together..... including at the output of [pack s s s].
So for one message composed of 3 variables do not put [$1 $2 $3( but put instead [$1$2$3(
Sort of the same principle as that you used with the [else/format list]] arguments to create a single date message.
[soundfiler] and [readsf~] are both a bit "picky" about the messages they receive and do need explicit symbols.
Try [symbol $1$2( where $1 and $2 are directory and filemame from the previous message.... banged into [write $1( where $1 is now the combined path and filename.... and send that to [soundfiler].
David.
P.S. If you start your message with "symbol" or "list" or another tag then the message sent onwards prepended by "set" is not broken up into separate "symbol" and "message".
The tag takes effect on the following message and is silent in the next message window.
If you [print] that following message then you will see that.
For a "list" tag...... which is automatically applied by the [list] objects..... often it is necessary afterwards to remove the tag with [list trim] to get back to a symbol..... as you can see in some of the examples above.
Standard tags (also called "selector") are list, symbol, float (implied) and pointer.
A PITA is that a text string is considered "text" unless tagged as a symbol.
If you are unsure of the tag then you can use "a" for "anything" in a trigger and the message will pass...... as in [t b a].
You can also add your own tags to a message..... which is what you are doing when you send [this woof( into [route this that].
It is a little complicated, but essentially the first atom in a message is considered to be the tag and the following atoms parts of a list, except........
It is nasty. I made this tag.zip to help me through the quagmire..... especially to remember to use [bug_protected] when necessary as a list sent into [route] will change its arguments when unrecognised.
It is horrible...... e.g. you need to know that list is stripped if the list starts with a symbol......
but added back if it starts with a float....... but that doesn't show up in a [print].
You need [rawprint] to see that..... maybe the most useful external ever made...?......
bang~ order
seems like it.
looking at sched_tick
and the 2 different schedulers in m_sched.c
it seems like the order is:
- Gui is polled (where pd gets the 'click' message from tcl/tk, triggering the
[bng]
to output) - clock callbacks occur (this is where the
[bang~]
's bang gets output) for the 'next' 64 samples - 'current' scheduler time is incremented 64 samples
- audio is processed for the block (this is where
[bang~]
sets the clock callback for 0 logical time)
really, since [bang~]
happens every cycle we should think about step 4 happening 1st because there will be a callback already on the set clock list from the dsp cycle before pd gets the click from the gui. so
4(4). [bang~]
sets a clock callback for 0 logical time.
5(1). pd gets the 'click' event (still at that time) so [timer]
is set and [spigot]
is opened
6(2). the clock callbacks are triggered, and the clock callback for [bang~]
triggers for logical time 0 (relative to the 'click'), outputting and triggering the [timer]
7(3). logical system time is incremented for the next dsp tick
Javascript pdjs - how to send a message with commas in it?
Greetings All,
I am trying to write a JavaScript program using pdjs that allows me to send messages with commas to a synth. Something like this:
| 60, 64, 67 (
|
I have a string synthesizer and when I send these three MIDI values I get a C chord. This works as expected. I have tried to emulate this in many different ways in a pdjs JavaScript file. Please see attached. If I run the first line where I send the three values one after another things work as expected. I have tried a number of different ways to create a message with commas as above, with no luck. I am probably missing something easy and would appreciate any pointers in the right direction.
inlets = 1;
outlets = 1;
function bang() {
// if send individually works fine...
messnamed("notes", 60); messnamed("notes", 64); messnamed("notes", 67); //<-works!
// none of these work
messnamed("notes", ["60", "64", "67"]); //<-no method for '60'
messnamed("notes", [60, 64, 67]); //<-plays only first note
messnamed("notes", "60, 64, 67"); //<-no method for '60, 64, 67'
messnamed("notes", 60, 64, 67); //<-plays only first note
messnamed("notes", "60", "64", "67"); //<-no method for '60'
messnamed("notes", "60, "addcomma", 64", "addcomma", "67"); //<-won't compile
messnamed("notes", ["list", 60, 64, 67]); //<-plays only first note
messnamed("notes", ["list", 60, "list", 64, "list", 67]); //<-plays only first note
messnamed("notes", ["list", "60", "64", "67"]); //<-does nothing
}
Thanks in advance for your help.!
port granulator from max/msp to pd
I used this tool https://github.com/tmhglnd/max-pd-converter to try to convert the .maxpat
then I put a [declare -lib cyclone]
object and replaced [tapin~]
/[tapout~]
with [delwrite~]
/[vd~]
replaced the [if ]
with a simple [< ]
changed the [inlet]
to [inlet~]
and [outlet]
to [outlet~]
grain.pd
seems like the result is similar to what you already had though.. maybe the issue is your [clone]
usage..
edit: forgot the comma for the message box..