# at.atmosynth.module

> Published by [atmosynth.at](https://lexicon.garden/identity/did:plc:fzjkahpwapmoqxybwdjb6ysf)

✓ This is the authoritative definition for this NSID.

## Description

A reusable synth module: Web Audio nodes wired together, with declared audio and MIDI boundaries, exposed parameters, automation and sample slots. Each published version is its own record; records sharing a moduleId form a series.

## Links

- [View on Lexicon Garden](https://lexicon.garden/lexicon/did:plc:fzjkahpwapmoqxybwdjb6ysf/at.atmosynth.module)
- [Documentation](https://lexicon.garden/lexicon/did:plc:fzjkahpwapmoqxybwdjb6ysf/at.atmosynth.module/docs)
- [Examples](https://lexicon.garden/lexicon/did:plc:fzjkahpwapmoqxybwdjb6ysf/at.atmosynth.module/examples)

## Definitions

### `at.atmosynth.module#automation`

**Type**: `object`

A breakpoint envelope scheduled onto an AudioParam. Breakpoints up to and including sustainAt are scheduled on note-on; those after it on note-off.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `breakpoints` | `array` | Yes |  |
| `id` | `string` | Yes |  |
| `label` | `string` | No |  |
| `sustainAt` | `integer` | No | Index into breakpoints where the value holds while a note is held. Absent means a one-shot. |
| `target` | `ref` → `#paramTarget` | Yes |  |

### `at.atmosynth.module#breakpoint`

**Type**: `object`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `curve` | `string` | No | How the value travels from the previous breakpoint. |
| `time` | `integer` | Yes | Milliseconds from the trigger. |
| `value` | `string` | Yes | The param's value at this instant, as its decimal form — "0.25", "1" — because the ATproto data model has no floating-point type and a PDS refuses one. An automation is numeric by definition, so a reader turns it back into a number without asking anything else. |

### `at.atmosynth.module#connection`

**Type**: `object`

An audio connection from a node output to either an audio input or a named AudioParam.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `from` | `ref` → `#source` | Yes |  |
| `to` | `ref` → `#target` | Yes |  |

### `at.atmosynth.module#lineage`

**Type**: `object`

The module this one was forked from, and its author.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `authorDid` | `string` (did) | Yes |  |
| `moduleId` | `string` | No |  |
| `uri` | `string` (at-uri) | Yes |  |

### `at.atmosynth.module`

**Type**: `record`

A module, held in its author's own repository.

**Key**: `tid`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `audioInputs` | `array` | Yes | Zero or more individually identified audio inputs. |
| `audioOutputs` | `array` | Yes |  |
| `automation` | `array` | No | Scheduled AudioParam envelopes. Web Audio has no envelope node, so these are their own structure. |
| `connections` | `array` | Yes |  |
| `createdAt` | `string` (datetime) | Yes |  |
| `description` | `string` | Yes |  |
| `descriptionVersion` | `integer` | Yes | Version of the module description this record follows. A reader accepts any version at or below its own. |
| `forkedFrom` | `ref` → `#lineage` | No |  |
| `midiInputs` | `array` | Yes | Zero or more note inputs. A note input carries note events — what is played, and when — from any note source: a sequencer's note output, or the player, which is the on-screen keys, the computer keyboard and every MIDI device at once. "MIDI" is the historical name; nothing arriving here need have come from MIDI. |
| `moduleId` | `string` | Yes | Stable identity shared by every published version of this module. |
| `name` | `string` | Yes |  |
| `nodes` | `array` | Yes |  |
| `noteOutputs` | `array` | No | Zero or more note outputs. A note output carries note events — what is played, and when — rather than a signal, so it is wired to another instance's note input (`midiInputs`) rather than to an audio input. A module that declares one is a source of what is played; a module that declares none is what plays it. |
| `noteSource` | `string` | No | What this module emits notes by, where it is not made of Web Audio nodes at all. A note source has no `nodes` and no `connections`: it produces note events, and the behaviour that produces them is named here rather than described, because there is no vocabulary of nodes for a thing that makes no sound. `range` emits none of its own: it passes on the notes wired into its input that fall inside the range its instance names (`noteRange` on the patch's instance), each still the note of whatever played it. `transpose` and `assign` emit none of their own either: they pass on every note wired into their input at another pitch — moved by the semitones the instance names (`transpose`), or at the one note it names (`assignedNote`) — each still the note of whatever played it, and starting and stopping when it did. `oneshot` emits none of its own either: it passes on every note wired into its input lasting as many milliseconds as the instance names (`noteLength`), however long whatever played it held it — each still the note of whatever played it, at its own pitch, and starting when it did. A reader that does not know the named behaviour cannot run the module, and says so rather than guessing. |
| `parameters` | `array` | No |  |
| `patternSource` | `string` | No | What this module chooses a sequencer's loops by, where it is not made of Web Audio nodes at all. A pattern source emits neither sound nor notes: it is wired to a sequencer's pattern input and decides which of that sequencer's loops is played, and when. Like a note source it has no `nodes` and no `connections`, and its behaviour is named here rather than described. A module is a note source or a pattern source, never both. A reader that does not know the named behaviour cannot run the module, and says so rather than guessing. |
| `sampleSlots` | `array` | No |  |
| `versionKey` | `string` | No | The record key this version is published under, repeated in the record so a reader holding only the record knows which version it is. A patch's `moduleRef.versionKey` is this value, and it is the record key a reader fetches: a record whose `versionKey` differs from its own key is pinned by the key and misdescribed by this field. |

### `at.atmosynth.module#node`

**Type**: `object`

One Web Audio node. `kind` is drawn from a closed vocabulary; `options` are its construction-time settings, which are not AudioParams and cannot be modulated.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `id` | `string` | Yes |  |
| `kind` | `string` | Yes |  |
| `options` | `unknown` | No | The node's settings, as its kind defines them. This is an object — which is the whole of what `unknown` means here — so the data model applies inside it: a whole number is an ordinary integer, and a number that is not a whole number is carried as its decimal string, "0.5" rather than 0.5, because the ATproto data model has no floating-point type and a PDS refuses one. That is the one difference from the fields this description types as strings, which have nowhere to put an integer. |

### `at.atmosynth.module#paramTarget`

**Type**: `object`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `node` | `string` | Yes |  |
| `param` | `string` | Yes |  |

### `at.atmosynth.module#parameter`

**Type**: `object`

A parameter the module exposes for a patch to set. Each target reaches an AudioParam, a construction option, or one field of an automation breakpoint.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `default` | `string` | No | The value used where a patch sets none, always as text. A number is its decimal form — "0.5", and "2" for a whole one — because the ATproto data model has no floating-point type and a string is the only type that carries both a number and a word. Where this parameter declares a closed value set, it is one of those strings instead, and a label that looks like a number stays the label it is. |
| `id` | `string` | Yes |  |
| `label` | `string` | Yes |  |
| `range` | `ref` → `#range` | No |  |
| `scale` | `string` | No | How a slider or a MIDI control moves through the range. linear moves by equal differences; log moves by equal ratios, which is how a frequency is heard, so the middle of a 20 to 20000 sweep is about 632 rather than about 10000. log needs a range above zero at both ends. Absent is linear. A parameter with a closed value set has no scale. |
| `targets` | `array` | Yes |  |
| `values` | `array` | No | A closed value set, where the parameter is not numeric. A construction option the parameter targets is given the chosen value itself; an AudioParam it targets is given the chosen value's position in this set, counting from zero, which is how a worklet offers a choice its own code interprets. |

### `at.atmosynth.module#parameterTarget`

**Type**: `object`

One of: {node, param}, {node, option}, or {automation, breakpoint, field}.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `automation` | `string` | No |  |
| `breakpoint` | `integer` | No |  |
| `field` | `string` | No |  |
| `node` | `string` | No |  |
| `option` | `string` | No |  |
| `param` | `string` | No |  |

### `at.atmosynth.module#port`

**Type**: `object`

One individually identified audio or MIDI boundary of the module.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `id` | `string` | Yes |  |
| `label` | `string` | No |  |

### `at.atmosynth.module#range`

**Type**: `object`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `max` | `string` | Yes | The high bound, as its decimal form — "20000", "0.5" — because the ATproto data model has no floating-point type and a PDS refuses one. A range is numeric by definition, so a reader turns it back into a number without asking anything else. |
| `min` | `string` | Yes | The low bound, as its decimal form — "0.0001", "-1" — because the ATproto data model has no floating-point type and a PDS refuses one. A range is numeric by definition, so a reader turns it back into a number without asking anything else. |

### `at.atmosynth.module#sampleRef`

**Type**: `object`

A sample blob and the account that holds it.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `blob` | `blob` | Yes |  |
| `ownerDid` | `string` (did) | Yes |  |

### `at.atmosynth.module#sampleSlot`

**Type**: `object`

A named slot a patch instance may fill with its own sample. `node` names a node in this graph and `property` the thing the sample feeds.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `default` | `ref` → `#sampleRef` | No |  |
| `id` | `string` | Yes |  |
| `label` | `string` | No |  |
| `node` | `string` | Yes |  |
| `property` | `string` | Yes |  |

### `at.atmosynth.module#source`

**Type**: `object`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `node` | `string` | Yes |  |
| `output` | `integer` | No |  |

### `at.atmosynth.module#target`

**Type**: `object`

Exactly one of `input` or `param` is present. `param` is modulation: a node output reaching a named AudioParam.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `input` | `integer` | No |  |
| `node` | `string` | Yes |  |
| `param` | `string` | No |  |

## Raw Schema

```json
{
  "$type": "com.atproto.lexicon.schema",
  "defs": {
    "automation": {
      "description": "A breakpoint envelope scheduled onto an AudioParam. Breakpoints up to and including sustainAt are scheduled on note-on; those after it on note-off.",
      "properties": {
        "breakpoints": {
          "items": {
            "ref": "#breakpoint",
            "type": "ref"
          },
          "minLength": 1,
          "type": "array"
        },
        "id": {
          "maxLength": 64,
          "minLength": 1,
          "type": "string"
        },
        "label": {
          "maxLength": 200,
          "type": "string"
        },
        "sustainAt": {
          "description": "Index into breakpoints where the value holds while a note is held. Absent means a one-shot.",
          "minimum": 0,
          "type": "integer"
        },
        "target": {
          "ref": "#paramTarget",
          "type": "ref"
        }
      },
      "required": [
        "id",
        "target",
        "breakpoints"
      ],
      "type": "object"
    },
    "breakpoint": {
      "properties": {
        "curve": {
          "description": "How the value travels from the previous breakpoint.",
          "knownValues": [
            "step",
            "linear",
            "exponential"
          ],
          "type": "string"
        },
        "time": {
          "description": "Milliseconds from the trigger.",
          "minimum": 0,
          "type": "integer"
        },
        "value": {
          "description": "The param's value at this instant, as its decimal form — \"0.25\", \"1\" — because the ATproto data model has no floating-point type and a PDS refuses one. An automation is numeric by definition, so a reader turns it back into a number without asking anything else.",
          "maxLength": 200,
          "type": "string"
        }
      },
      "required": [
        "time",
        "value"
      ],
      "type": "object"
    },
    "connection": {
      "description": "An audio connection from a node output to either an audio input or a named AudioParam.",
      "properties": {
        "from": {
          "ref": "#source",
          "type": "ref"
        },
        "to": {
          "ref": "#target",
          "type": "ref"
        }
      },
      "required": [
        "from",
        "to"
      ],
      "type": "object"
    },
    "lineage": {
      "description": "The module this one was forked from, and its author.",
      "properties": {
        "authorDid": {
          "format": "did",
          "type": "string"
        },
        "moduleId": {
          "maxLength": 128,
          "type": "string"
        },
        "uri": {
          "format": "at-uri",
          "type": "string"
        }
      },
      "required": [
        "uri",
        "authorDid"
      ],
      "type": "object"
    },
    "main": {
      "description": "A module, held in its author's own repository.",
      "key": "tid",
      "record": {
        "properties": {
          "audioInputs": {
            "description": "Zero or more individually identified audio inputs.",
            "items": {
              "ref": "#port",
              "type": "ref"
            },
            "type": "array"
          },
          "audioOutputs": {
            "items": {
              "ref": "#port",
              "type": "ref"
            },
            "type": "array"
          },
          "automation": {
            "description": "Scheduled AudioParam envelopes. Web Audio has no envelope node, so these are their own structure.",
            "items": {
              "ref": "#automation",
              "type": "ref"
            },
            "type": "array"
          },
          "connections": {
            "items": {
              "ref": "#connection",
              "type": "ref"
            },
            "type": "array"
          },
          "createdAt": {
            "format": "datetime",
            "type": "string"
          },
          "description": {
            "maxLength": 3000,
            "type": "string"
          },
          "descriptionVersion": {
            "description": "Version of the module description this record follows. A reader accepts any version at or below its own.",
            "minimum": 1,
            "type": "integer"
          },
          "forkedFrom": {
            "ref": "#lineage",
            "type": "ref"
          },
          "midiInputs": {
            "description": "Zero or more note inputs. A note input carries note events — what is played, and when — from any note source: a sequencer's note output, or the player, which is the on-screen keys, the computer keyboard and every MIDI device at once. \"MIDI\" is the historical name; nothing arriving here need have come from MIDI.",
            "items": {
              "ref": "#port",
              "type": "ref"
            },
            "type": "array"
          },
          "moduleId": {
            "description": "Stable identity shared by every published version of this module.",
            "maxLength": 128,
            "type": "string"
          },
          "name": {
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          },
          "nodes": {
            "items": {
              "ref": "#node",
              "type": "ref"
            },
            "type": "array"
          },
          "noteOutputs": {
            "description": "Zero or more note outputs. A note output carries note events — what is played, and when — rather than a signal, so it is wired to another instance's note input (`midiInputs`) rather than to an audio input. A module that declares one is a source of what is played; a module that declares none is what plays it.",
            "items": {
              "ref": "#port",
              "type": "ref"
            },
            "type": "array"
          },
          "noteSource": {
            "description": "What this module emits notes by, where it is not made of Web Audio nodes at all. A note source has no `nodes` and no `connections`: it produces note events, and the behaviour that produces them is named here rather than described, because there is no vocabulary of nodes for a thing that makes no sound. `range` emits none of its own: it passes on the notes wired into its input that fall inside the range its instance names (`noteRange` on the patch's instance), each still the note of whatever played it. `transpose` and `assign` emit none of their own either: they pass on every note wired into their input at another pitch — moved by the semitones the instance names (`transpose`), or at the one note it names (`assignedNote`) — each still the note of whatever played it, and starting and stopping when it did. `oneshot` emits none of its own either: it passes on every note wired into its input lasting as many milliseconds as the instance names (`noteLength`), however long whatever played it held it — each still the note of whatever played it, at its own pitch, and starting when it did. A reader that does not know the named behaviour cannot run the module, and says so rather than guessing.",
            "knownValues": [
              "keyboard",
              "sequencer",
              "range",
              "transpose",
              "assign",
              "oneshot"
            ],
            "maxLength": 64,
            "type": "string"
          },
          "parameters": {
            "items": {
              "ref": "#parameter",
              "type": "ref"
            },
            "type": "array"
          },
          "patternSource": {
            "description": "What this module chooses a sequencer's loops by, where it is not made of Web Audio nodes at all. A pattern source emits neither sound nor notes: it is wired to a sequencer's pattern input and decides which of that sequencer's loops is played, and when. Like a note source it has no `nodes` and no `connections`, and its behaviour is named here rather than described. A module is a note source or a pattern source, never both. A reader that does not know the named behaviour cannot run the module, and says so rather than guessing.",
            "knownValues": [
              "arranger",
              "scenes"
            ],
            "maxLength": 64,
            "type": "string"
          },
          "sampleSlots": {
            "items": {
              "ref": "#sampleSlot",
              "type": "ref"
            },
            "type": "array"
          },
          "versionKey": {
            "description": "The record key this version is published under, repeated in the record so a reader holding only the record knows which version it is. A patch's `moduleRef.versionKey` is this value, and it is the record key a reader fetches: a record whose `versionKey` differs from its own key is pinned by the key and misdescribed by this field.",
            "maxLength": 128,
            "type": "string"
          }
        },
        "required": [
          "descriptionVersion",
          "moduleId",
          "name",
          "description",
          "audioInputs",
          "audioOutputs",
          "midiInputs",
          "nodes",
          "connections",
          "createdAt"
        ],
        "type": "object"
      },
      "type": "record"
    },
    "node": {
      "description": "One Web Audio node. `kind` is drawn from a closed vocabulary; `options` are its construction-time settings, which are not AudioParams and cannot be modulated.",
      "properties": {
        "id": {
          "maxLength": 64,
          "minLength": 1,
          "type": "string"
        },
        "kind": {
          "maxLength": 64,
          "minLength": 1,
          "type": "string"
        },
        "options": {
          "description": "The node's settings, as its kind defines them. This is an object — which is the whole of what `unknown` means here — so the data model applies inside it: a whole number is an ordinary integer, and a number that is not a whole number is carried as its decimal string, \"0.5\" rather than 0.5, because the ATproto data model has no floating-point type and a PDS refuses one. That is the one difference from the fields this description types as strings, which have nowhere to put an integer.",
          "type": "unknown"
        }
      },
      "required": [
        "id",
        "kind"
      ],
      "type": "object"
    },
    "paramTarget": {
      "properties": {
        "node": {
          "maxLength": 64,
          "minLength": 1,
          "type": "string"
        },
        "param": {
          "maxLength": 64,
          "minLength": 1,
          "type": "string"
        }
      },
      "required": [
        "node",
        "param"
      ],
      "type": "object"
    },
    "parameter": {
      "description": "A parameter the module exposes for a patch to set. Each target reaches an AudioParam, a construction option, or one field of an automation breakpoint.",
      "properties": {
        "default": {
          "description": "The value used where a patch sets none, always as text. A number is its decimal form — \"0.5\", and \"2\" for a whole one — because the ATproto data model has no floating-point type and a string is the only type that carries both a number and a word. Where this parameter declares a closed value set, it is one of those strings instead, and a label that looks like a number stays the label it is.",
          "maxLength": 200,
          "type": "string"
        },
        "id": {
          "maxLength": 64,
          "minLength": 1,
          "type": "string"
        },
        "label": {
          "maxLength": 200,
          "minLength": 1,
          "type": "string"
        },
        "range": {
          "ref": "#range",
          "type": "ref"
        },
        "scale": {
          "description": "How a slider or a MIDI control moves through the range. linear moves by equal differences; log moves by equal ratios, which is how a frequency is heard, so the middle of a 20 to 20000 sweep is about 632 rather than about 10000. log needs a range above zero at both ends. Absent is linear. A parameter with a closed value set has no scale.",
          "knownValues": [
            "linear",
            "log"
          ],
          "type": "string"
        },
        "targets": {
          "items": {
            "ref": "#parameterTarget",
            "type": "ref"
          },
          "minLength": 1,
          "type": "array"
        },
        "values": {
          "description": "A closed value set, where the parameter is not numeric. A construction option the parameter targets is given the chosen value itself; an AudioParam it targets is given the chosen value's position in this set, counting from zero, which is how a worklet offers a choice its own code interprets.",
          "items": {
            "maxLength": 200,
            "type": "string"
          },
          "type": "array"
        }
      },
      "required": [
        "id",
        "label",
        "targets"
      ],
      "type": "object"
    },
    "parameterTarget": {
      "description": "One of: {node, param}, {node, option}, or {automation, breakpoint, field}.",
      "properties": {
        "automation": {
          "maxLength": 64,
          "type": "string"
        },
        "breakpoint": {
          "minimum": 0,
          "type": "integer"
        },
        "field": {
          "knownValues": [
            "time",
            "value"
          ],
          "type": "string"
        },
        "node": {
          "maxLength": 64,
          "type": "string"
        },
        "option": {
          "maxLength": 64,
          "type": "string"
        },
        "param": {
          "maxLength": 64,
          "type": "string"
        }
      },
      "type": "object"
    },
    "port": {
      "description": "One individually identified audio or MIDI boundary of the module.",
      "properties": {
        "id": {
          "maxLength": 64,
          "minLength": 1,
          "type": "string"
        },
        "label": {
          "maxLength": 200,
          "type": "string"
        }
      },
      "required": [
        "id"
      ],
      "type": "object"
    },
    "range": {
      "properties": {
        "max": {
          "description": "The high bound, as its decimal form — \"20000\", \"0.5\" — because the ATproto data model has no floating-point type and a PDS refuses one. A range is numeric by definition, so a reader turns it back into a number without asking anything else.",
          "maxLength": 200,
          "type": "string"
        },
        "min": {
          "description": "The low bound, as its decimal form — \"0.0001\", \"-1\" — because the ATproto data model has no floating-point type and a PDS refuses one. A range is numeric by definition, so a reader turns it back into a number without asking anything else.",
          "maxLength": 200,
          "type": "string"
        }
      },
      "required": [
        "min",
        "max"
      ],
      "type": "object"
    },
    "sampleRef": {
      "description": "A sample blob and the account that holds it.",
      "properties": {
        "blob": {
          "accept": [
            "audio/wav"
          ],
          "maxSize": 10485760,
          "type": "blob"
        },
        "ownerDid": {
          "format": "did",
          "type": "string"
        }
      },
      "required": [
        "blob",
        "ownerDid"
      ],
      "type": "object"
    },
    "sampleSlot": {
      "description": "A named slot a patch instance may fill with its own sample. `node` names a node in this graph and `property` the thing the sample feeds.",
      "properties": {
        "default": {
          "ref": "#sampleRef",
          "type": "ref"
        },
        "id": {
          "maxLength": 64,
          "minLength": 1,
          "type": "string"
        },
        "label": {
          "maxLength": 200,
          "type": "string"
        },
        "node": {
          "maxLength": 64,
          "minLength": 1,
          "type": "string"
        },
        "property": {
          "knownValues": [
            "buffer"
          ],
          "type": "string"
        }
      },
      "required": [
        "id",
        "node",
        "property"
      ],
      "type": "object"
    },
    "source": {
      "properties": {
        "node": {
          "maxLength": 64,
          "minLength": 1,
          "type": "string"
        },
        "output": {
          "minimum": 0,
          "type": "integer"
        }
      },
      "required": [
        "node"
      ],
      "type": "object"
    },
    "target": {
      "description": "Exactly one of `input` or `param` is present. `param` is modulation: a node output reaching a named AudioParam.",
      "properties": {
        "input": {
          "minimum": 0,
          "type": "integer"
        },
        "node": {
          "maxLength": 64,
          "minLength": 1,
          "type": "string"
        },
        "param": {
          "maxLength": 64,
          "type": "string"
        }
      },
      "required": [
        "node"
      ],
      "type": "object"
    }
  },
  "description": "A reusable synth module: Web Audio nodes wired together, with declared audio and MIDI boundaries, exposed parameters, automation and sample slots. Each published version is its own record; records sharing a moduleId form a series.",
  "id": "at.atmosynth.module",
  "lexicon": 1
}
```
