{
  "openapi": "3.1.1",
  "info": {
    "title": "Tonegrid Pattern Renderer API",
    "version": "1.0.0",
    "summary": "Render a Tonegrid project to a stereo WAV file and measure the mix.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://tonegridpatternrender.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Render a Tonegrid project to a stereo WAV file and measure the mix.",
        "description": "Turns a Tonegrid project — the JSON file or a share link — into a 16-bit stereo WAV file: one seamless pattern loop or the whole song arrangement, with the Tonegrid synth voices, drums, pan and reverb send. The result reports peak and RMS levels per channel, crest factor, clipped samples, the strongest spectral lines, a spectrum chart and a waveform overview. It is the same renderer as the WAV export in the Tonegrid app, so both give identical files.\n\nMethodology:\n- Every step is a sixteenth note at the project tempo; swing delays every second step. Melody notes and drum hits are synthesized sample by sample at 44.1 kHz with the same recipes the app plays live through Web Audio: sine, triangle and band-limited (PolyBLEP) sawtooth oscillators, exponential attack and decay envelopes, an exponentially falling low-pass on the pluck, pitch-swept kick, tom and rim, and high-passed seeded noise for snare, hats, clap and shaker.\n- Mixer: each track has a level and an equal-power pan (with +3 dB make-up so a centred track is as loud as before), and a send into one mono reverb bus. The reverb is the Freeverb-style room reverb of the shared audio engine with the chosen preset’s reverberation time, 100 % wet; the master volume scales the sum.\n- Pattern range: exactly one full cycle (until melody and drum grids line up again); everything that rings past the end — long pad notes and the reverb tail — is added back onto the start, so the loop repeats seamlessly. Song range: all parts in order plus at least 1.5 s ring-out (longer with reverb) and a 10 ms fade at the very end.\n- The mix is written as 16-bit PCM WAV (rounded, no dither; samples beyond full scale clip and are counted). Levels, crest factor, the spectrum (Hann window, 4096-point Welch average of the mono mix) and the waveform come from the shared audio engine analysis.\n\nTonegrid sounds are simple synthesizers. Live playback in a browser uses Web Audio and can differ very slightly from this file (oscillator tables, reverb impulse length).",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "tonegrid_pattern_render",
        "x-owner-app": "tonegrid",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TonegridPatternRenderInput"
              },
              "examples": {
                "example1": {
                  "summary": "Lo-fi pattern loop with pad reverb and the beat panned right",
                  "value": {
                    "project": "{\"format\":\"tonegrid\",\"version\":3,\"name\":\"Lo-fi evening\",\"tempo\":82,\"swing\":28,\"root\":57,\"scale\":\"dorian\",\"voice\":\"pad\",\"volume\":0.7,\"melodyLevel\":0.8,\"drumLevel\":0.7,\"melodyMute\":false,\"drumMute\":false,\"melodyPan\":0,\"drumPan\":0.3,\"reverb\":\"living-room\",\"melodyReverb\":0.35,\"drumReverb\":0.08,\"rows\":16,\"steps\":16,\"drumRows\":4,\"drumSteps\":16,\"patterns\":[{\"cells\":[[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false],[false,false,true,false,false,false,false,false,false,false,true,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false],[false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false]],\"drumCells\":[[true,false,false,false,false,false,true,false,true,false,false,false,false,false,false,false],[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false],[true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false]]},{\"cells\":[[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,true,false,false,false,true,false,false,false,false,false,false,false],[false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false]],\"drumCells\":[[true,false,false,false,false,false,true,false,true,false,false,false,false,false,true,false],[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false],[true,false,true,false,true,false,true,false,true,false,true,false,true,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false]]}],\"current\":0,\"song\":[{\"pattern\":0,\"repeats\":2},{\"pattern\":1,\"repeats\":1},{\"pattern\":0,\"repeats\":1}]}",
                    "range": "pattern"
                  }
                },
                "example2": {
                  "summary": "Three-part song in a concert hall",
                  "value": {
                    "project": "{\"format\":\"tonegrid\",\"version\":3,\"name\":\"Three-part song\",\"tempo\":112,\"swing\":12,\"root\":50,\"scale\":\"major\",\"voice\":\"bell\",\"volume\":0.7,\"melodyLevel\":0.8,\"drumLevel\":0.75,\"melodyMute\":false,\"drumMute\":false,\"melodyPan\":0,\"drumPan\":0,\"reverb\":\"hall\",\"melodyReverb\":0.25,\"drumReverb\":0.1,\"rows\":16,\"steps\":16,\"drumRows\":6,\"drumSteps\":16,\"patterns\":[{\"cells\":[[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false]],\"drumCells\":[[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false]]},{\"cells\":[[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false],[false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[true,false,false,false,false,false,true,false,true,false,false,false,false,false,false,false],[false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false],[true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false]],\"drumCells\":[[true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false],[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false],[true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false]]},{\"cells\":[[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false],[false,false,true,false,true,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,true,false,false,false,true,false,false,false,true,false],[true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[true,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false]],\"drumCells\":[[true,false,false,false,false,false,true,false,true,false,false,false,false,false,true,false],[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false],[true,false,true,false,true,false,true,false,true,false,true,false,true,false,true,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,true,false,false,false,false,false,false,false,true,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true]]}],\"current\":0,\"song\":[{\"pattern\":0,\"repeats\":2},{\"pattern\":1,\"repeats\":2},{\"pattern\":2,\"repeats\":2},{\"pattern\":1,\"repeats\":2},{\"pattern\":2,\"repeats\":2}]}",
                    "range": "song"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TonegridPatternRenderResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool": {
      "get": {
        "operationId": "getToolDefinition",
        "summary": "Inputs, output schema and examples for this tool",
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "TonegridPatternRenderInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "project": {
            "title": "Tonegrid project (JSON or share link)",
            "description": "Paste the content of a .tonegrid.json file, the project object returned by the Tonegrid Pattern Builder, or a Tonegrid share link (…/app#project=…).",
            "type": "string",
            "maxLength": 200000
          },
          "range": {
            "title": "Range",
            "type": "string",
            "enum": [
              "pattern",
              "song"
            ],
            "default": "pattern"
          }
        },
        "required": [
          "project"
        ]
      },
      "TonegridPatternRenderResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "name",
              "range",
              "sampleRate",
              "channels",
              "musicSeconds",
              "seconds",
              "seamlessLoop",
              "events",
              "reverb",
              "reverbTailSeconds",
              "levels",
              "peaks",
              "wavFile"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "range": {
                "type": "string",
                "enum": [
                  "pattern",
                  "song"
                ]
              },
              "sampleRate": {
                "type": "integer"
              },
              "channels": {
                "type": "integer"
              },
              "musicSeconds": {
                "type": "number",
                "description": "Length of the music (pattern cycle or song) in seconds."
              },
              "seconds": {
                "type": "number",
                "description": "Length of the WAV file (song: music + ring-out; pattern: exactly one cycle)."
              },
              "seamlessLoop": {
                "type": "boolean",
                "description": "True for pattern loops: notes and reverb that ring past the end are folded onto the start, so the file repeats without a gap."
              },
              "events": {
                "type": "integer",
                "description": "Notes and drum hits rendered."
              },
              "reverb": {
                "type": "string",
                "description": "Room preset of the reverb send, or \"none\"."
              },
              "reverbTailSeconds": {
                "type": "number"
              },
              "levels": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "peakDb",
                  "rmsDb",
                  "crestDb",
                  "leftPeakDb",
                  "rightPeakDb",
                  "leftRmsDb",
                  "rightRmsDb",
                  "clippedSamples",
                  "centroidHz",
                  "rolloffHz"
                ],
                "properties": {
                  "peakDb": {
                    "type": "number",
                    "description": "Sample peak of the mix in dBFS (−120 = silent)."
                  },
                  "rmsDb": {
                    "type": "number"
                  },
                  "crestDb": {
                    "type": "number"
                  },
                  "leftPeakDb": {
                    "type": "number"
                  },
                  "rightPeakDb": {
                    "type": "number"
                  },
                  "leftRmsDb": {
                    "type": "number"
                  },
                  "rightRmsDb": {
                    "type": "number"
                  },
                  "clippedSamples": {
                    "type": "integer",
                    "description": "Samples at or above full scale (they clip in the 16-bit file)."
                  },
                  "centroidHz": {
                    "type": "number"
                  },
                  "rolloffHz": {
                    "type": "number"
                  }
                }
              },
              "peaks": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "frequencyHz",
                    "levelDb"
                  ],
                  "properties": {
                    "frequencyHz": {
                      "type": "number"
                    },
                    "levelDb": {
                      "type": "number"
                    }
                  }
                }
              },
              "wavFile": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "filename",
                  "mime",
                  "bytes",
                  "sha256",
                  "path"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Download path on the same site (valid for about 2 hours)."
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}