Dictionaries#

The .dict family: keyed, nested data.

13 objects. Every entry is pulled directly from the engine source via the ADD_DESCRIPTION / INLET_DOC / OUTLET_DOC / PARAM_DOC macros (see YseEngine/patcher/pObject.h). The ~ prefix marks DSP / audio-rate objects; . marks control-rate objects.

.dict#

A key/value store shared by name. Keys nest with ‘::’ (voice::1::freq), a value is any message, and the contents are saved with the patch. A bang sends the reference ‘dictionary <name>’ that the other .dict objects take.

Kind:

Control-rate

A dictionary holds entries: a key path and a value. Keys nest with ::, so voice::1::freq is one entry three levels deep. A value is the text of any message, so set chord 0 4 7 stores one value of three numbers. See Nested keys in Collections, dictionaries and arrays for how the nesting works.

Messages

  • set <path> <value...>: store a value, replacing the one already there. set <path> with no value stores an empty value.

  • get <path>: send the value out of outlet 0. One number leaves as an int or a float, anything else as a list. When the path is missing, outlet 2 bangs instead. A path holding an empty value sends nothing at all.

  • delete <path>: remove one entry. A sub-tree is not an entry: delete voice does not remove voice::1::freq (use .dict.strip).

  • clear: remove every entry.

  • getsize: send the number of entries out of outlet 0.

  • getkeys: send the top-level keys out of outlet 0, each once, in the order they were first written.

  • bang: send the reference dictionary <name> out of outlet 1.

Sharing

Every .dict with the same name in a patcher uses the same dictionary, and two patchers with the same name share theirs. A .dict without a name keeps a private dictionary and sends no reference. The contents are saved with the patch as nested JSON. See Sharing a store by name and What is saved with the patch.

The other .dict objects

The other objects in this category each work on one or more named dictionaries. They take the names as creation arguments, and the names cannot be changed while the patch runs. A cord cannot carry a dictionary, so an object that produces one writes it into a dictionary you name, rather than sending it (see Dictionaries and arrays travel by name). They share these rules:

  • A bang on the left inlet runs the object. So does the reference dictionary <name> of the dictionary it reads, so outlet 1 of a .dict can be wired straight into it. A reference that names any other dictionary is refused.

  • Any extra inlet accepts the reference of the dictionary it stands for and does nothing with it, so the wiring can show which dictionaries an object uses.

  • An object that writes a dictionary replaces its whole contents (.dict.strip edits it in place), then sends that dictionary’s reference. So the objects chain: the reference that leaves one can run the next.

  • Each run works on the dictionary as it was when the run started. Changes made while it sends, even by objects it is wired to, show up only on the next run.

  • An object without a name works on a private, empty dictionary and sends no reference.

set voice::1::freq 440  ->  [.dict synth]
bang                    ->  [.dict synth]              outlet 1: dictionary synth
dictionary synth        ->  [.dict.serialize synth]    ->  {"voice":{"1":{"freq":440}}}

Limits

A dictionary holds at most 256 entries. A key path is at most 128 characters, including the :: separators, and a value at most 256. A message that does not fit is refused: it changes nothing, sends nothing and is not logged. The same refusal happens when two threads use one dictionary at the same moment: the second one does nothing rather than wait. See Size limits.

Safe on the audio thread: no message allocates memory, takes a lock or waits. Only .dict.deserialize does part of its work elsewhere.

Inlets

#

Label

Accepts

Range

Description

0

in

BANG, LIST

at most 256 entries

‘set <path> <value…>’ stores a value, ‘get <path>’ sends one, ‘delete <path>’ removes one, ‘clear’ empties the dictionary, ‘getsize’ sends the entry count and ‘getkeys’ the top-level keys. A bang sends the reference out of outlet 1.

Outlets

#

Label

Type

Range

Description

0

data

ANY

—

The value a ‘get’ fetched: one number leaves as an int or a float, anything else as a list. Also the count from ‘getsize’ and the key list from ‘getkeys’.

1

reference

LIST

—

‘dictionary <name>’ on a bang: the reference the other .dict objects accept. Silent for an unnamed dictionary.

2

miss

BANG

—

Bang when a ‘get’ names a path the dictionary does not hold.

Parameters

Name

Default

Range

Description

name

—

any identifier

The dictionary’s name. Every .dict with this name in the patcher shares one dictionary. Empty gives this object a private dictionary. Cannot be changed while the patch runs.

.dict.compare#

Compares two named dictionaries and sends 1 when they hold the same entries, 0 when they do not. On a difference, the key paths that differ follow from the right outlet.

Kind:

Control-rate

Two dictionaries are equal when they hold the same key paths with the same values, in any order. Values are compared as text, so 7 and 7. differ.

On a difference, the paths that differ follow the verdict: first the paths that are only in the left dictionary or hold a different value, in the left dictionary’s order, then the paths that are only in the right one. The verdict is sent first, so a patch can act on it before the list arrives.

Both arguments may name the same dictionary. At most 256 paths are sent.

Inlets

#

Label

Accepts

Range

Description

0

compare

BANG, LIST

—

A bang compares the two dictionaries. So does the left dictionary’s reference ‘dictionary <name>’.

1

right reference

LIST

—

Accepts the right dictionary’s reference and does nothing with it, so its .dict can be wired here.

Outlets

#

Label

Type

Range

Description

0

equal

INT

0 or 1

1 when the two dictionaries hold the same entries, 0 when they do not. Sent before the differing paths.

1

paths

ANY

at most 256 paths

The key paths that differ, as a list, sent after the verdict. Silent when the dictionaries are equal.

Parameters

Name

Default

Range

Description

left

—

any identifier

The name of the left dictionary. Empty compares an empty dictionary.

right

—

any identifier

The name of the right dictionary. Empty compares an empty dictionary.

.dict.deserialize#

Fills a named dictionary from JSON text, replacing its contents, and sends its reference. Takes a JSON object as a message, or ‘read <file>’ for a larger document.

Kind:

Control-rate

The message must be one JSON object, such as the text .dict.serialize sends. It replaces the contents of the named dictionary, and the reference then leaves the outlet:

  • Nested objects become :: key paths.

  • An array becomes a list value. Arrays and objects inside an array are dropped.

  • true and false become 1 and 0; null becomes an empty value.

The JSON is read in the background, so the dictionary changes and the reference is sent one audio block after the message arrives, not at once. A document that is not valid JSON, or is not an object, changes nothing and sends nothing. A document that arrives while the previous one is still being read is refused.

Files

A message holds at most 255 characters. For a larger document use read <file>, which loads a JSON file of up to 128 KiB and then works like a message. A bare read reads the last file again. See File input and output.

Entries past 256, or with a key path or value that is too long, are dropped and the rest of the document still loads.

Inlets

#

Label

Accepts

Range

Description

0

json

LIST

at most 255 characters inline; a file up to 128 KiB

A JSON object as one list message, such as the text .dict.serialize sends. It replaces the dictionary’s contents one block later. ‘read <file>’ loads the JSON from a file; a bare ‘read’ reads the last file again.

Outlets

#

Label

Type

Range

Description

0

reference

LIST

—

‘dictionary <name>’ once a document has been loaded. Silent for an unnamed dictionary and for a document that could not be read.

Parameters

Name

Default

Range

Description

name

—

any identifier

The name of the dictionary to fill. Empty fills a private dictionary and sends nothing.

.dict.group#

Groups a named dictionary’s entries by value into a target dictionary, as ‘<group>::<original path>’. With a key argument, each record is grouped by the value it holds at that key.

Kind:

Control-rate

Each entry of the source is written into the target as <group>::<original path>, with its value unchanged. Without a key argument, an entry’s group is its own value, which turns a table into an index. With a key argument, the source is read as records: the first part of each path names a record, and every entry of the record groups under the value the record holds at that key.

source notes:   n1::inst bass   n1::pitch 40   n2::inst lead   n2::pitch 72
[.dict.group notes byinst inst]
target byinst:  bass::n1::inst bass   bass::n1::pitch 40
                lead::n2::inst lead   lead::n2::pitch 72

An entry is skipped when its group is empty, when its record has no entry at the key, or when the new path would be longer than 128 characters.

Inlets

#

Label

Accepts

Range

Description

0

group

BANG, LIST

—

A bang groups the source dictionary into the target and sends the target’s reference. So does the source dictionary’s reference ‘dictionary <name>’.

1

target reference

LIST

—

Accepts the target dictionary’s reference and does nothing with it, so its .dict can be wired here.

Outlets

#

Label

Type

Range

Description

0

reference

LIST

—

‘dictionary <target>’ after each grouping. Silent for an unnamed target.

Parameters

Name

Default

Range

Description

source

—

any identifier

The name of the dictionary to group. Empty groups an empty dictionary.

target

—

any identifier

The name of the dictionary that receives the result. Each grouping replaces its contents.

key

—

any key path segment

The key each record’s group is read from: with ‘instrument’, the entries under ‘<record>::’ group under the value at ‘<record>::instrument’. Empty groups each entry by its own value.

.dict.iter#

Walks a named dictionary, sending one ‘<path> <value>’ list per entry, then a bang from the right outlet when it is done.

Kind:

Control-rate

Each entry leaves as one list: its whole :: path, then its value. The entries leave in the order they were first written. When the last one has been sent, the right outlet bangs, also for an empty dictionary.

Each pair is handled completely, including everything it causes downstream, before the next pair leaves. Use the done bang for whatever should happen after the walk. A run that arrives while a walk is still sending is refused, so an outlet wired back into the inlet does not restart the walk.

Inlets

#

Label

Accepts

Range

Description

0

walk

BANG, LIST

—

A bang walks the dictionary: one pair per entry out of the left outlet, then a bang out of the right one. So does the dictionary’s reference ‘dictionary <name>’.

Outlets

#

Label

Type

Range

Description

0

pair

LIST

—

One entry per message: its whole ‘::’ path followed by its value, in the order the entries were first written. An entry with an empty value leaves as its path alone.

1

done

BANG

—

Bang after the last pair, also for an empty dictionary.

Parameters

Name

Default

Range

Description

name

—

any identifier

The name of the dictionary to walk. Empty walks an empty dictionary: no pairs, only the done bang.

.dict.join#

Merges two named dictionaries into a target dictionary: the left one’s entries, overlaid with the right one’s. Where both hold a key path, the right value wins.

Kind:

Control-rate

The target gets every entry of the left dictionary, then every entry of the right one. Where both hold the same key path, the right value replaces the left one, so the left dictionary is the base and the right one the override. Neither source is changed, unless it is also the target: .dict.join base preset base merges preset into base.

When the result would be more than 256 entries, the new key paths of the right dictionary that do not fit are dropped; the rest of the join still happens.

Inlets

#

Label

Accepts

Range

Description

0

join

BANG, LIST

—

A bang joins the two dictionaries into the target and sends the target’s reference. So does the left dictionary’s reference ‘dictionary <name>’.

1

right reference

LIST

—

Accepts the right dictionary’s reference and does nothing with it, so its .dict can be wired here.

Outlets

#

Label

Type

Range

Description

0

reference

LIST

—

‘dictionary <target>’ after each join. Silent for an unnamed target.

Parameters

Name

Default

Range

Description

left

—

any identifier

The name of the base dictionary. Empty joins an empty dictionary.

right

—

any identifier

The name of the dictionary laid over the base: its value wins on a shared key path. Empty joins an empty dictionary.

target

—

any identifier

The name of the dictionary that receives the result. Each join replaces its contents. It may be one of the two sources.

.dict.pack#

Builds a named dictionary from its inlets, one inlet per key path: ‘.dict.pack synth freq gain’. The left inlet stores its value, writes the dictionary and sends its reference; the other inlets only store.

Kind:

Control-rate

The first argument names the dictionary, and every argument after it is a key path with its own inlet. The inlets work like those of .pack (see Lists): the left inlet stores its value and writes, the others only store, so set the right-hand values first.

[.dict.pack synth freq gain]   inlet 1 <- 0.5   inlet 0 <- 440
dictionary synth now holds:    freq 440   gain 0.5

Messages

  • A value on any inlet is stored for that inlet’s key path. A whole list is one value, not spread over the inlets.

  • bang on the left inlet: write the dictionary without storing anything.

  • set <value>: store a value without writing.

  • The reference dictionary <name> of the bound dictionary writes it on the left inlet and is ignored on the others.

Writing replaces the whole dictionary with one entry per key path, so any other key in it is removed. A key path that has not received a value yet is written with an empty value. With no key paths, a bang writes an empty dictionary. When the same key path is given twice, the inlet further right wins.

At most 256 key paths of at most 128 characters; a longer path is logged and gets no inlet. A value longer than 256 characters is refused and the inlet keeps its old value.

Inlets

#

Label

Accepts

Range

Description

0

pack

BANG, LIST

—

With no key paths, a bang writes an empty dictionary and sends its reference.

Outlets

#

Label

Type

Range

Description

0

reference

LIST

—

‘dictionary <name>’ after each write. Silent for an unnamed dictionary.

Parameters

Name

Default

Range

Description

name

—

any identifier

The name of the dictionary to write. Empty writes a private dictionary and sends nothing.

keys

—

key paths

One key path per inlet, in order. Paths nest with ‘::’. A path longer than 128 characters is logged and gets no inlet.

.dict.print#

Prints a named dictionary to the engine log as indented JSON, one line per row, each line labelled with the dictionary’s name. A debugging aid with no outlets.

Kind:

Control-rate

The dictionary is printed as indented JSON, the same structure a saved patch holds. Each line starts with the dictionary’s name and a colon:

synth: {
synth:   "voice": {
synth:     "1": {
synth:       "freq": 440
synth:     }
synth:   }
synth: }

The lines reach the engine log the next time the host calls YSE::System().update(). YSE::Log().setLogfile() and YSE::Log().setHandler() choose where the log goes.

Limits

The second argument limits how many lines the object sends per update, 64 by default and 1 to 256. When a dump runs past it, the rest is dropped and one line says that the limit was reached. A line too long for the log is cut and ends in .... Only the first 32 characters of the name are used as the label.

Inlets

#

Label

Accepts

Range

Description

0

dump

BANG, LIST

—

A bang prints the dictionary to the engine log. So does the dictionary’s reference ‘dictionary <name>’.

Parameters

Name

Default

Range

Description

name

—

any identifier

The name of the dictionary to print, also used as the label in front of every line (at most 32 characters of it). Empty prints {} under the label ‘dict.print’.

lines

64

1-256

The most lines this object sends to the log per engine update. Lines past it are dropped and one notice line says so. A value that is not a number keeps the default.

.dict.route#

Sends a named dictionary’s reference out of the outlet of the first key argument it holds, or out of the rightmost outlet when it holds none. Use it to send dictionaries of different shapes to different parts of a patch.

Kind:

Control-rate

Each key argument has an outlet, and one more outlet on the right is added for no match. The dictionary is tested for each key in order, and its reference leaves the outlet of the first key it holds. Exactly one outlet sends per run.

A key matches an entry at that path or under it: voice matches voice::1::freq, but not voicing. A key can be a deeper path such as voice::1. When a key is given twice, only its first outlet is used.

The no-match outlet sends the same reference, so a second .dict.route wired to it can test more keys. Without a name the object sends nothing, because a private dictionary has no reference. A run that arrives while the object is still sending is refused, so an outlet wired back into the inlet does not loop.

Inlets

#

Label

Accepts

Range

Description

0

route

BANG, LIST

—

A bang routes the dictionary: its reference leaves the outlet of the first key argument it holds, or the rightmost outlet when it holds none. So does the dictionary’s reference ‘dictionary <name>’.

Outlets

#

Label

Type

Range

Description

0

none

LIST

—

The dictionary’s reference, when it holds none of the keys. Wire it into the next .dict.route to test more keys.

Parameters

Name

Default

Range

Description

name

—

any identifier

The name of the dictionary to route. Empty makes the object do nothing.

keys

—

key paths

One key per outlet, in order. A key may be a path (‘voice::1’); it matches an entry at that path or under it. The rightmost outlet is always added for no match.

.dict.serialize#

Sends a named dictionary as one line of JSON text. ‘write <file>’ saves the same JSON to a file instead, for a dictionary too large to send as a message.

Kind:

Control-rate

The JSON is written on one line with no spaces outside strings. :: paths become nested objects, and the entries keep the order they were first written. Values are written as:

  • one number: a JSON number;

  • several tokens: an array;

  • an empty value: "";

  • anything else: a string.

An empty dictionary is {}. .dict.deserialize reads this text back into a dictionary.

Limits

A message holds at most 255 characters. When the JSON is longer, nothing is sent. Use write <file> instead, which saves the same JSON to a file of up to 128 KiB, or walk the entries with .dict.iter. A bare write writes to the last file again. See File input and output.

Inlets

#

Label

Accepts

Range

Description

0

serialize

BANG, LIST

—

A bang sends the dictionary as JSON text. So does the dictionary’s reference ‘dictionary <name>’. ‘write <file>’ saves the same JSON to a file instead; a bare ‘write’ writes to the last file again.

Outlets

#

Label

Type

Range

Description

0

json

LIST

—

The dictionary as one line of compact JSON, in one list message. Nothing is sent when the text would be longer than 255 characters.

Parameters

Name

Default

Range

Description

name

—

any identifier

The name of the dictionary to convert. Empty converts an empty dictionary, which gives {}.

.dict.slice#

Splits a named dictionary at a key path. Entries under the path go into the slice dictionary with the path removed from their keys; all other entries go into the remainder dictionary.

Kind:

Control-rate

An entry is under the path when its key starts with the path followed by ::. Those entries go into the slice dictionary with that start removed, so the sub-tree becomes a dictionary of its own. Every other entry goes into the remainder dictionary unchanged.

source:     voice::1::freq 440   voice::1::gain 0.5   voice::2::freq 220
[.dict.slice synth v1 rest voice::1]
v1:         freq 440   gain 0.5
rest:       voice::2::freq 220
  • An entry stored at exactly the path is a value, not a sub-tree. It goes into the remainder.

  • voice does not match voices::1: the path ends at a ::.

  • An empty path matches nothing, so the remainder gets everything.

The remainder’s reference is sent first, then the slice’s. When both targets have the same name, that dictionary ends up holding the remainder.

Inlets

#

Label

Accepts

Range

Description

0

slice

BANG, LIST

—

A bang splits the source dictionary into the slice and remainder dictionaries and sends both references. So does the source dictionary’s reference ‘dictionary <name>’.

1

slice reference

LIST

—

Accepts the slice dictionary’s reference and does nothing with it, so its .dict can be wired here.

2

remainder reference

LIST

—

Accepts the remainder dictionary’s reference and does nothing with it, so its .dict can be wired here.

Outlets

#

Label

Type

Range

Description

0

slice reference

LIST

—

‘dictionary <slice>’ after each split, sent after the remainder. Silent for an unnamed slice dictionary.

1

remainder reference

LIST

—

‘dictionary <remainder>’ after each split, sent first. Silent for an unnamed remainder dictionary.

Parameters

Name

Default

Range

Description

source

—

any identifier

The name of the dictionary to split. Empty splits an empty dictionary.

slice

—

any identifier

The name of the dictionary that receives the entries under the path, with the path removed. Each split replaces its contents.

remainder

—

any identifier

The name of the dictionary that receives every other entry, keys unchanged. Each split replaces its contents.

path

—

any key path

The key path to split at: ‘voice::1’ takes every ‘voice::1::…’ entry. Empty takes nothing.

.dict.strip#

Removes every entry under a key path from a named dictionary, in place, then sends its reference. An entry stored at exactly the path is kept.

Kind:

Control-rate

The entries removed are exactly the ones .dict.slice would put in its slice for the same path: every entry whose key starts with the path followed by ::. An entry at exactly the path is kept, and voice does not match voices::1. An empty path removes nothing.

To remove one entry, send delete <path> to .dict; to remove everything, send it clear.

Inlets

#

Label

Accepts

Range

Description

0

strip

BANG, LIST

—

A bang removes every entry under the path and sends the dictionary’s reference. So does the dictionary’s reference ‘dictionary <name>’.

Outlets

#

Label

Type

Range

Description

0

dictionary reference

LIST

—

‘dictionary <name>’ after each strip. Silent for an unnamed dictionary.

Parameters

Name

Default

Range

Description

name

—

any identifier

The name of the dictionary to edit. Empty strips an empty dictionary.

path

—

any key path

The key path whose entries are removed: ‘voice::1’ removes every ‘voice::1::…’ entry. An entry at exactly the path is kept. Empty removes nothing.

.dict.unpack#

Sends the values at a named dictionary’s key paths out of separate outlets, one outlet per key path argument, right to left. The inverse of .dict.pack.

Kind:

Control-rate

The first argument names the dictionary, and every argument after it is a key path with its own outlet. Each value leaves its outlet right to left, so a .dict.unpack can feed the cold inlets of an object before its hot one. One number leaves as an int or a float, anything else as one list.

A missing path, or one holding an empty value, sends nothing on its outlet. When the same path is given twice, both outlets send. A run that arrives while the object is still sending is refused.

.dict.pack and .dict.unpack with the same name and key paths are a pair: a reference from the .dict.pack makes the .dict.unpack send the values that were packed. At most 256 key paths of at most 128 characters; a longer path is logged and gets no outlet.

Inlets

#

Label

Accepts

Range

Description

0

unpack

BANG, LIST

—

A bang sends the value of each key path out of its outlet, right to left. So does the dictionary’s reference ‘dictionary <name>’.

Parameters

Name

Default

Range

Description

name

—

any identifier

The name of the dictionary to read. Empty reads an empty dictionary, so nothing is sent.

keys

—

key paths

One key path per outlet, in order. Paths nest with ‘::’. A path longer than 128 characters is logged and gets no outlet.