{
  "openapi": "3.1.1",
  "info": {
    "title": "Tonegrid API",
    "version": "1.0.0",
    "summary": "Deterministic tools for people and AI agents.",
    "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://tonegrid.usefulweb.app"
    }
  ],
  "paths": {
    "/api/v1/tools": {
      "get": {
        "operationId": "listTools",
        "summary": "Catalog of all tools",
        "responses": {
          "200": {
            "description": "Tool list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/{id}": {
      "get": {
        "operationId": "getTool",
        "summary": "Definition of one tool",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "tonegrid-pattern-builder",
                "tonegrid-scale-planner",
                "audio-converter",
                "audio-cutter-merger",
                "audio-loudness-normalizer"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/tonegrid-pattern-builder/calculate": {
      "post": {
        "operationId": "calculateTonegridPatternBuilder",
        "summary": "Turn x-and-dot step notation into a Tonegrid project and a MIDI file.",
        "description": "Write a melody and a beat as lines of x and dots, one line per note name or drum. The builder checks every note against the chosen key and scale, returns an editable Tonegrid project with a link that opens it in the app, and a Standard MIDI File with the melody and General MIDI drums.\n\nMethodology:\n- Each melody line is matched to a row of the grid: the root note is the lowest row and the scale repeats upward, so a note that is not in the key and range is rejected with the range that would fit.\n- Steps are sixteenth notes. The melody and drum grids may have different lengths; they line up again after the least common multiple of both lengths (rounded up to an even number of steps for swing).\n- The MIDI file (type 1, 480 ticks per quarter note) repeats the pattern as often as requested. Melody notes are on channel 1 with a General MIDI instrument that matches the sound; drums are on channel 10 with General MIDI percussion keys (kick 36, snare 38, closed hi-hat 42, open hi-hat 46, clap 39, low tom 45, rim 37, maracas 70). Swing delays odd steps by the swing share of a sixteenth.\n\nThe MIDI file contains notes only; the Tonegrid synth sounds and mixer settings are not part of it, so your music software chooses the instrument sounds.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "tonegrid_pattern_builder",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TonegridPatternBuilderInput"
              },
              "examples": {
                "example1": {
                  "summary": "Pentatonic melody with a simple beat",
                  "value": {
                    "name": "Ripple beat",
                    "pattern": "# melody (note names) and drums: x = hit, . = rest\nG4    .... ..x. .... ..x.\nE4    .... x... .... x...\nA3    ..x. .... ..x. ....\nE3    x... .... x... ....\nkick  x... .... x... ....\nsnare .... x... .... x...\nclosed-hat x.x. x.x. x.x. x.x."
                  }
                },
                "example2": {
                  "summary": "Twelve-step melody against a sixteen-step beat",
                  "value": {
                    "name": "Phase sketch",
                    "root": 57,
                    "scale": "minorPentatonic",
                    "bpm": 96,
                    "swing": 20,
                    "repeats": 3,
                    "pattern": "E5 x... ..x. ....\nC5 ..x. .... x...\nA4 .... x... ..x.\nA3 x... .... ....\nkick x... ..x. x... ....\nhat ..x. ..x. ..x. ..x."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TonegridPatternBuilderResult"
                }
              }
            }
          },
          "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/tools/tonegrid-scale-planner/calculate": {
      "post": {
        "operationId": "calculateTonegridScalePlanner",
        "summary": "Map a scale to a note grid and plan your melody.",
        "description": "Inspect the pitches in a configurable Tonegrid scale and turn selected cells into a timed note list.\n\nMethodology:\n- Repeat the chosen scale intervals upward from the root and place the lowest pitch on the last row.\n- Sixteenth-note steps use the same tempo and swing timing as Tonegrid. Cells in the same column play together.\n- Note frequencies use 12-tone equal temperament with A4 at 440 Hz.\n\nThe result shows the first melody cycle only, not an audio rendering or drum sequence. For odd step counts, swing alternates between cycles. Timing assumes a constant tempo.",
        "tags": [
          "apps"
        ],
        "x-mcp-tool": "tonegrid_scale_planner",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TonegridScalePlannerInput"
              },
              "examples": {
                "example1": {
                  "summary": "C major pentatonic melody",
                  "value": {
                    "root": 48,
                    "scale": "pentatonic",
                    "bpm": 120,
                    "swing": 0,
                    "cells": "1,16\n5,14\n9,12\n13,11"
                  }
                },
                "example2": {
                  "summary": "A minor pentatonic chord with swing",
                  "value": {
                    "root": 57,
                    "scale": "minorPentatonic",
                    "bpm": 90,
                    "swing": 30,
                    "cells": "1,16\n1,14\n1,13\n4,11"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TonegridScalePlannerResult"
                }
              }
            }
          },
          "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/tools/audio-converter/calculate": {
      "post": {
        "operationId": "calculateAudioConverter",
        "summary": "Convert MP3, WAV, FLAC, Ogg, Opus, M4A and AIFF with bitrate and sample-rate control.",
        "description": "Converts audio between MP3, WAV, FLAC, Ogg Vorbis, Ogg Opus, M4A (AAC) and AIFF with ffmpeg. Choose constant bitrate or VBR quality, sample rate, mono/stereo and bit depth; tags and album covers are kept where the format supports them. Shows an ffprobe report before and after, with a player and a download.\n\nMethodology:\n- The container is detected from the first bytes of the file (not the name) and passed to ffmpeg explicitly, so playlists or files that reference other files are never opened.\n- ffprobe reads codec, sample rate, channels, bitrate, duration and tags before conversion. ffmpeg then decodes the first audio track and encodes it with LAME (MP3), the native AAC encoder (M4A), libvorbis, libopus, FLAC or PCM (WAV/AIFF).\n- Lossy formats use either a fixed/average bitrate or a VBR quality level (MP3 -q:a, Vorbis -q:a; Opus/AAC use VBR at a matching target bitrate). Lossless formats ignore the bitrate.\n- Opus only supports 8/12/16/24/48 kHz and MP3 at most 48 kHz; other rates are resampled to the nearest supported rate and you get a warning.\n- Tags are copied with -map_metadata 0; album covers are copied for MP3, M4A and FLAC (Ogg/WAV/AIFF covers are not supported by ffmpeg muxers). Encoder version tags and timestamps are not written, so the same input always gives the same file.\n- ffprobe reads the result again for the before/after report, including stream-level tags (Ogg Vorbis/Opus store tags per stream); tagsKept is only true when every source tag is found in the result. The reported sample rate is the one of the finished file — Opus always decodes at 48 kHz.\n\nConverting a lossy file (MP3, AAC, Ogg) to another lossy format or to a lossless format cannot restore lost quality.",
        "tags": [
          "media"
        ],
        "x-mcp-tool": "audio_converter",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AudioConverterInput"
              },
              "examples": {
                "example1": {
                  "summary": "MP3 tone to Ogg Opus 64 kbit/s, mono",
                  "value": {
                    "file": {
                      "filename": "tone.mp3",
                      "contentType": "audio/mpeg",
                      "contentBase64": "SUQzAwAAAAAANFRJVDIAAAALAAAAVGVzdCB0b25lAFRQRTEAAAALAAAAdXNlZnVsd2ViAAAAAAAAAAAAAAD/4zjAAAAAAAAAAAAASW5mbwAAAA8AAAAJAAADYABVVVVVVVVVVVVVVWpqampqampqampqgICAgICAgICAgICVlZWVlZWVlZWVlaqqqqqqqqqqqqqqwMDAwMDAwMDAwMDV1dXV1dXV1dXV1erq6urq6urq6urq//////////////8AAAAATGF2ZiBsYW1lAAAAAAAAAAAAAAAAJAJgAAAAAAAAA2DhWsr/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/4xjEAAyokqh5SAAAD22/4d3d3v/RESv5d3f5SSwbgLgLgLg3PlAxw/yhz9/R7+j38HAxkAffWBDmgH+Ud39CgQkKRFiBEz//4xjECA3w6qDBlDgA5MkBJoc7/EBhyQNsGAOBS4BzxgokpgaxKNMIQcg9HhsNv/x0ajU4GhL/hIGhKdBq////////////////4xjECw8QpjgB3gAA+GhGAAYBYAxfoGgIGBqDgYSYPhhOi9GJrekZAAshh+g1GCWCYAgOwqAOYCAAJbFkl+rfoA//b7/L5fX/4xjECQqwjo5YCbRi/n3qiICUpOT5DnL9WR4Ms63+X06P/v9H/+//+nJf+t9GXtsQBtdfrcl5/f+V1eIwQL/nESYRBr0i8sr/4xjEGQtIloJYCnZigON9/lP9n9b/T9v37q/9P2//1fRV///5O3qXQOATAhMCmwMhwSPQYQHRBgcjAEgOoWAuAIB4YAGRACL/4xjEJgpYpjQABvxEbLUb6v///jGAGAADfQMAIBwMCwEgEAvAxGjLAx9m1A6uxbA3pD+AwbAgAwDAJACBeAkAoj4ZgQohCDD/4xjENw04pkABVgAAAR/itBmDQVp/WLIJwRwGKJAwM/DsNjcZwDM5yDpBHgAgGAy0hSC/v8A3wDWMsT8L2PP//zMe5udL5uv/4xjEPRaRlozJlWgAL///+brMwdB8Bn//oB8BmiY1//9BMaaJuNf//3IVBd2AA//aqqp/9VVVOdqOJAwVLAwCARKyIBJV/QX/4xjEHQ1AmqR5xggBBn/0FCv8FBn/FBQp/xQYK/oKFHfiSf/9TEFNRTQuMSAoYWxwaGEgMClVVVVVVVVVVVVVVVVVVVVVVVU="
                    },
                    "format": "opus",
                    "bitrate": "64",
                    "channels": "1"
                  }
                },
                "example2": {
                  "summary": "MP3 to lossless FLAC with tags",
                  "value": {
                    "file": {
                      "filename": "tone.mp3",
                      "contentType": "audio/mpeg",
                      "contentBase64": "SUQzAwAAAAAANFRJVDIAAAALAAAAVGVzdCB0b25lAFRQRTEAAAALAAAAdXNlZnVsd2ViAAAAAAAAAAAAAAD/4zjAAAAAAAAAAAAASW5mbwAAAA8AAAAJAAADYABVVVVVVVVVVVVVVWpqampqampqampqgICAgICAgICAgICVlZWVlZWVlZWVlaqqqqqqqqqqqqqqwMDAwMDAwMDAwMDV1dXV1dXV1dXV1erq6urq6urq6urq//////////////8AAAAATGF2ZiBsYW1lAAAAAAAAAAAAAAAAJAJgAAAAAAAAA2DhWsr/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/4xjEAAyokqh5SAAAD22/4d3d3v/RESv5d3f5SSwbgLgLgLg3PlAxw/yhz9/R7+j38HAxkAffWBDmgH+Ud39CgQkKRFiBEz//4xjECA3w6qDBlDgA5MkBJoc7/EBhyQNsGAOBS4BzxgokpgaxKNMIQcg9HhsNv/x0ajU4GhL/hIGhKdBq////////////////4xjECw8QpjgB3gAA+GhGAAYBYAxfoGgIGBqDgYSYPhhOi9GJrekZAAshh+g1GCWCYAgOwqAOYCAAJbFkl+rfoA//b7/L5fX/4xjECQqwjo5YCbRi/n3qiICUpOT5DnL9WR4Ms63+X06P/v9H/+//+nJf+t9GXtsQBtdfrcl5/f+V1eIwQL/nESYRBr0i8sr/4xjEGQtIloJYCnZigON9/lP9n9b/T9v37q/9P2//1fRV///5O3qXQOATAhMCmwMhwSPQYQHRBgcjAEgOoWAuAIB4YAGRACL/4xjEJgpYpjQABvxEbLUb6v///jGAGAADfQMAIBwMCwEgEAvAxGjLAx9m1A6uxbA3pD+AwbAgAwDAJACBeAkAoj4ZgQohCDD/4xjENw04pkABVgAAAR/itBmDQVp/WLIJwRwGKJAwM/DsNjcZwDM5yDpBHgAgGAy0hSC/v8A3wDWMsT8L2PP//zMe5udL5uv/4xjEPRaRlozJlWgAL///+brMwdB8Bn//oB8BmiY1//9BMaaJuNf//3IVBd2AA//aqqp/9VVVOdqOJAwVLAwCARKyIBJV/QX/4xjEHQ1AmqR5xggBBn/0FCv8FBn/FBQp/xQYK/oKFHfiSf/9TEFNRTQuMSAoYWxwaGEgMClVVVVVVVVVVVVVVVVVVVVVVVU="
                    },
                    "format": "flac"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AudioConverterResult"
                }
              }
            }
          },
          "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/tools/audio-cutter-merger/calculate": {
      "post": {
        "operationId": "calculateAudioCutterMerger",
        "summary": "Trim audio, keep or remove ranges, fade in/out, or join up to 10 clips with crossfades.",
        "description": "Cuts an audio file to a start and end time, keeps or removes a list of time ranges, and adds fade-in/fade-out. In merge mode it joins up to 10 clips in order, optionally with a crossfade between them. Output as MP3, M4A, Ogg, Opus, FLAC or WAV.\n\nMethodology:\n- Times are parsed strictly (seconds, mm:ss or hh:mm:ss.mmm); only the resulting numbers are written into the ffmpeg filtergraph, never your text.\n- Cut mode: ranges to keep are sorted, clipped to the file length and merged where they overlap; \"remove\" ranges are inverted into the parts to keep. Each part is cut sample-accurately with atrim and the parts are joined with the concat filter.\n- Merge mode: every clip is converted to a common sample rate (the highest of the clips, max 48 kHz) and channel layout (stereo if any clip is stereo), then joined with concat, or chained with acrossfade (triangular curves) when a crossfade is set. Each crossfade shortens the total by its length.\n- Fade in/out (afade) is applied to the final result. The result is encoded once in the chosen format.",
        "tags": [
          "media"
        ],
        "x-mcp-tool": "audio_cutter_merger",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AudioCutterMergerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Cut 0.1–0.4 s with a short fade out",
                  "value": {
                    "mode": "cut",
                    "file": {
                      "filename": "tone.mp3",
                      "contentType": "audio/mpeg",
                      "contentBase64": "SUQzAwAAAAAANFRJVDIAAAALAAAAVGVzdCB0b25lAFRQRTEAAAALAAAAdXNlZnVsd2ViAAAAAAAAAAAAAAD/4zjAAAAAAAAAAAAASW5mbwAAAA8AAAAJAAADYABVVVVVVVVVVVVVVWpqampqampqampqgICAgICAgICAgICVlZWVlZWVlZWVlaqqqqqqqqqqqqqqwMDAwMDAwMDAwMDV1dXV1dXV1dXV1erq6urq6urq6urq//////////////8AAAAATGF2ZiBsYW1lAAAAAAAAAAAAAAAAJAJgAAAAAAAAA2DhWsr/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/4xjEAAyokqh5SAAAD22/4d3d3v/RESv5d3f5SSwbgLgLgLg3PlAxw/yhz9/R7+j38HAxkAffWBDmgH+Ud39CgQkKRFiBEz//4xjECA3w6qDBlDgA5MkBJoc7/EBhyQNsGAOBS4BzxgokpgaxKNMIQcg9HhsNv/x0ajU4GhL/hIGhKdBq////////////////4xjECw8QpjgB3gAA+GhGAAYBYAxfoGgIGBqDgYSYPhhOi9GJrekZAAshh+g1GCWCYAgOwqAOYCAAJbFkl+rfoA//b7/L5fX/4xjECQqwjo5YCbRi/n3qiICUpOT5DnL9WR4Ms63+X06P/v9H/+//+nJf+t9GXtsQBtdfrcl5/f+V1eIwQL/nESYRBr0i8sr/4xjEGQtIloJYCnZigON9/lP9n9b/T9v37q/9P2//1fRV///5O3qXQOATAhMCmwMhwSPQYQHRBgcjAEgOoWAuAIB4YAGRACL/4xjEJgpYpjQABvxEbLUb6v///jGAGAADfQMAIBwMCwEgEAvAxGjLAx9m1A6uxbA3pD+AwbAgAwDAJACBeAkAoj4ZgQohCDD/4xjENw04pkABVgAAAR/itBmDQVp/WLIJwRwGKJAwM/DsNjcZwDM5yDpBHgAgGAy0hSC/v8A3wDWMsT8L2PP//zMe5udL5uv/4xjEPRaRlozJlWgAL///+brMwdB8Bn//oB8BmiY1//9BMaaJuNf//3IVBd2AA//aqqp/9VVVOdqOJAwVLAwCARKyIBJV/QX/4xjEHQ1AmqR5xggBBn/0FCv8FBn/FBQp/xQYK/oKFHfiSf/9TEFNRTQuMSAoYWxwaGEgMClVVVVVVVVVVVVVVVVVVVVVVVU="
                    },
                    "start": "0.1",
                    "end": "0.4",
                    "fadeOut": 0.1,
                    "format": "mp3",
                    "bitrate": "64"
                  }
                },
                "example2": {
                  "summary": "Merge two tones with a 0.1 s crossfade",
                  "value": {
                    "mode": "merge",
                    "file": {
                      "filename": "tone.mp3",
                      "contentType": "audio/mpeg",
                      "contentBase64": "SUQzAwAAAAAANFRJVDIAAAALAAAAVGVzdCB0b25lAFRQRTEAAAALAAAAdXNlZnVsd2ViAAAAAAAAAAAAAAD/4zjAAAAAAAAAAAAASW5mbwAAAA8AAAAJAAADYABVVVVVVVVVVVVVVWpqampqampqampqgICAgICAgICAgICVlZWVlZWVlZWVlaqqqqqqqqqqqqqqwMDAwMDAwMDAwMDV1dXV1dXV1dXV1erq6urq6urq6urq//////////////8AAAAATGF2ZiBsYW1lAAAAAAAAAAAAAAAAJAJgAAAAAAAAA2DhWsr/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/4xjEAAyokqh5SAAAD22/4d3d3v/RESv5d3f5SSwbgLgLgLg3PlAxw/yhz9/R7+j38HAxkAffWBDmgH+Ud39CgQkKRFiBEz//4xjECA3w6qDBlDgA5MkBJoc7/EBhyQNsGAOBS4BzxgokpgaxKNMIQcg9HhsNv/x0ajU4GhL/hIGhKdBq////////////////4xjECw8QpjgB3gAA+GhGAAYBYAxfoGgIGBqDgYSYPhhOi9GJrekZAAshh+g1GCWCYAgOwqAOYCAAJbFkl+rfoA//b7/L5fX/4xjECQqwjo5YCbRi/n3qiICUpOT5DnL9WR4Ms63+X06P/v9H/+//+nJf+t9GXtsQBtdfrcl5/f+V1eIwQL/nESYRBr0i8sr/4xjEGQtIloJYCnZigON9/lP9n9b/T9v37q/9P2//1fRV///5O3qXQOATAhMCmwMhwSPQYQHRBgcjAEgOoWAuAIB4YAGRACL/4xjEJgpYpjQABvxEbLUb6v///jGAGAADfQMAIBwMCwEgEAvAxGjLAx9m1A6uxbA3pD+AwbAgAwDAJACBeAkAoj4ZgQohCDD/4xjENw04pkABVgAAAR/itBmDQVp/WLIJwRwGKJAwM/DsNjcZwDM5yDpBHgAgGAy0hSC/v8A3wDWMsT8L2PP//zMe5udL5uv/4xjEPRaRlozJlWgAL///+brMwdB8Bn//oB8BmiY1//9BMaaJuNf//3IVBd2AA//aqqp/9VVVOdqOJAwVLAwCARKyIBJV/QX/4xjEHQ1AmqR5xggBBn/0FCv8FBn/FBQp/xQYK/oKFHfiSf/9TEFNRTQuMSAoYWxwaGEgMClVVVVVVVVVVVVVVVVVVVVVVVU="
                    },
                    "file2": {
                      "filename": "tone2.mp3",
                      "contentType": "audio/mpeg",
                      "contentBase64": "SUQzBAAAAAAACgAAAAAAAAAAAAD/4zjAAAAAAAAAAAAASW5mbwAAAA8AAAAJAAADYABVVVVVVVVVVVVVVWpqampqampqampqgICAgICAgICAgICVlZWVlZWVlZWVlaqqqqqqqqqqqqqqwMDAwMDAwMDAwMDV1dXV1dXV1dXV1erq6urq6urq6urq//////////////8AAAAATGF2ZiBsYW1lAAAAAAAAAAAAAAAAJAJgAAAAAAAAA2CfAwBHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/4xjEAAxI0phRSwAAArwHve973v9jGMYz+Xve9/8MYxlfy977QJAeAEwAwAwAwCwRyef3fv6P8CO4f5T39Co4bDYd/xqIo8P/4xjECQ6I9qABh9AAb/B8Og9Ab/xJ3i4QISh05d3yWWGCOD0Ki/F8hFQXddlMZjNampsTv8qIgqCoiPf+CtX/////9ZiTrBr/4xjECQ4w5kAB1kgAkBgLAcBgRE4Bg3DkBh3BEBhTHYBllKwB/bhaBnMKyBieA6IEAwFgWCgBw+UAwHiVdoWGABh/d9L+WXz/4xjECwxw1lcYDTyg/96/6iyBCGOqUu6gsaCAnw8CE0GRXujP/q+qz7v/9Wv/If9P/9Wt+upZwlQ4QGAbwCAEl7gMALGAlAD/4xjEFA0A5igABX6EeaMmFWhAOaNAJRMACrieyL09zfV/f///7v/9dv///+rMAAuAv/y//63caqKh7oFMhRFHgUzvgEvJhJz/4xjEGwsw4jWQCDxIrAOUZcyRyypv//7d3R//2/+t/////+tBZfcagAABADA3AYFAoAYZAfAYcRigZBRrgeE0lAZXB1AoKwL/4xjEKQzw5kgBVjAA4cDAOAsOeQcmLC0AAICBECgMBgQCf5hkz/FhhUFj/GgLgBQGiskgktEfoSVJ4DBGQWtkQIYMwIAAOQD/4xjEMBgJoq5ZiqAAFn5E///E3hicZMlRZZEyiOf///m6JfN0TMPA//+Aw8D4DNE//8aHiY00l3//5pLkKg3EQFdwAP/2Zl//4xjECg0wzrZZwxAB4zMyl7MzMv/GZv+MGAgImAgIUcUGG/iCnb6Cgw3+goUd8UGO/wUKG/5IKkxBTUU0LjEgKGFscGhhIDA="
                    },
                    "crossfade": 0.1,
                    "format": "wav"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AudioCutterMergerResult"
                }
              }
            }
          },
          "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/tools/audio-loudness-normalizer/calculate": {
      "post": {
        "operationId": "calculateAudioLoudnessNormalizer",
        "summary": "Measure LUFS and true peak, then normalise to Spotify, podcast or EBU R 128 targets.",
        "description": "Measures integrated loudness (LUFS), true peak (dBTP) and loudness range (LRA) with ffmpeg's EBU R 128 meter, then normalises the file in two passes to a streaming, podcast or broadcast target with a true-peak limit. Shows the measured values before and after, with a player and a download.\n\nMethodology:\n- Pass 1 measures the file with ffmpeg's loudnorm filter (ITU-R BS.1770 / EBU R 128 meter): integrated loudness (LUFS), true peak (dBTP, oversampled), loudness range (LRA) and the gating threshold.\n- Pass 2 feeds the measured values back into loudnorm with linear=true: if the whole file can reach the target with one gain change without exceeding the true-peak limit, only the gain changes (linear). Otherwise loudnorm switches to dynamic mode, which compresses and limits — the result reports which one happened.\n- loudnorm works at 192 kHz internally; the output is resampled back to the original sample rate. The podcast-mono preset mixes to mono first and measures and normalises the mono signal (both passes), because a mono mix-down has a different loudness than the stereo source.\n- Pass 3 measures the finished file again, so the \"after\" values are real measurements of the download, not the targets (for files over 15 minutes the normaliser's own output report is used instead, to stay within the time limit). Files up to 45 minutes are accepted.\n- Spotify (−14 LUFS, true peak below −1 dB) and EBU R 128 (−23 LUFS) are published targets; YouTube ≈ −14 and Apple ≈ −16 LUFS are widely used measured/recommended values, not contractual specs. Podcast −16 LUFS stereo / −19 LUFS mono are common industry recommendations.\n\nVery short files (under about 3 seconds) give less reliable integrated loudness, because the meter gates in 400 ms blocks.",
        "tags": [
          "media"
        ],
        "x-mcp-tool": "audio_loudness_normalizer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AudioLoudnessNormalizerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Normalise a test tone to the Spotify target",
                  "value": {
                    "file": {
                      "filename": "tone.mp3",
                      "contentType": "audio/mpeg",
                      "contentBase64": "SUQzAwAAAAAANFRJVDIAAAALAAAAVGVzdCB0b25lAFRQRTEAAAALAAAAdXNlZnVsd2ViAAAAAAAAAAAAAAD/4zjAAAAAAAAAAAAASW5mbwAAAA8AAAAJAAADYABVVVVVVVVVVVVVVWpqampqampqampqgICAgICAgICAgICVlZWVlZWVlZWVlaqqqqqqqqqqqqqqwMDAwMDAwMDAwMDV1dXV1dXV1dXV1erq6urq6urq6urq//////////////8AAAAATGF2ZiBsYW1lAAAAAAAAAAAAAAAAJAJgAAAAAAAAA2DhWsr/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/4xjEAAyokqh5SAAAD22/4d3d3v/RESv5d3f5SSwbgLgLgLg3PlAxw/yhz9/R7+j38HAxkAffWBDmgH+Ud39CgQkKRFiBEz//4xjECA3w6qDBlDgA5MkBJoc7/EBhyQNsGAOBS4BzxgokpgaxKNMIQcg9HhsNv/x0ajU4GhL/hIGhKdBq////////////////4xjECw8QpjgB3gAA+GhGAAYBYAxfoGgIGBqDgYSYPhhOi9GJrekZAAshh+g1GCWCYAgOwqAOYCAAJbFkl+rfoA//b7/L5fX/4xjECQqwjo5YCbRi/n3qiICUpOT5DnL9WR4Ms63+X06P/v9H/+//+nJf+t9GXtsQBtdfrcl5/f+V1eIwQL/nESYRBr0i8sr/4xjEGQtIloJYCnZigON9/lP9n9b/T9v37q/9P2//1fRV///5O3qXQOATAhMCmwMhwSPQYQHRBgcjAEgOoWAuAIB4YAGRACL/4xjEJgpYpjQABvxEbLUb6v///jGAGAADfQMAIBwMCwEgEAvAxGjLAx9m1A6uxbA3pD+AwbAgAwDAJACBeAkAoj4ZgQohCDD/4xjENw04pkABVgAAAR/itBmDQVp/WLIJwRwGKJAwM/DsNjcZwDM5yDpBHgAgGAy0hSC/v8A3wDWMsT8L2PP//zMe5udL5uv/4xjEPRaRlozJlWgAL///+brMwdB8Bn//oB8BmiY1//9BMaaJuNf//3IVBd2AA//aqqp/9VVVOdqOJAwVLAwCARKyIBJV/QX/4xjEHQ1AmqR5xggBBn/0FCv8FBn/FBQp/xQYK/oKFHfiSf/9TEFNRTQuMSAoYWxwaGEgMClVVVVVVVVVVVVVVVVVVVVVVVU="
                    },
                    "preset": "spotify",
                    "format": "wav"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AudioLoudnessNormalizerResult"
                }
              }
            }
          },
          "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/workflows": {
      "get": {
        "operationId": "listWorkflows",
        "summary": "Multi-tool workflows",
        "responses": {
          "200": {
            "description": "Workflow list.",
            "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"
                }
              }
            }
          }
        }
      },
      "TonegridPatternBuilderInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "title": "Project name",
            "type": "string",
            "maxLength": 100,
            "default": "API melody"
          },
          "pattern": {
            "title": "Pattern lines",
            "description": "One line per note (C4, D#4, Eb4) or drum (kick, snare, closed-hat, open-hat, clap, tom, rim, shaker), then x for a hit and a dot for a rest. Spaces and | are ignored; lines starting with # are comments. All melody lines need the same length, and so do all drum lines (1–32 steps).",
            "type": "string",
            "maxLength": 20000,
            "default": "# melody (note names) and drums: x = hit, . = rest\nG4    .... ..x. .... ..x.\nE4    .... x... .... x...\nA3    ..x. .... ..x. ....\nE3    x... .... x... ....\nkick  x... .... x... ....\nsnare .... x... .... x...\nclosed-hat x.x. x.x. x.x. x.x."
          },
          "root": {
            "title": "Key (root MIDI note)",
            "description": "48 is C3, 57 is A3, 60 is C4. The root is the lowest melody row.",
            "type": "number",
            "minimum": 48,
            "maximum": 71,
            "default": 48
          },
          "scale": {
            "title": "Scale",
            "type": "string",
            "enum": [
              "pentatonic",
              "minorPentatonic",
              "major",
              "minor",
              "dorian",
              "mixolydian",
              "harmonicMinor",
              "blues",
              "chromatic"
            ],
            "default": "pentatonic"
          },
          "rows": {
            "title": "Melody rows",
            "description": "How many scale notes the melody grid has, counted up from the root.",
            "type": "number",
            "minimum": 1,
            "maximum": 24,
            "default": 16
          },
          "voice": {
            "title": "Melody sound",
            "type": "string",
            "enum": [
              "bell",
              "sine",
              "triangle",
              "pluck",
              "pad"
            ],
            "default": "bell"
          },
          "bpm": {
            "title": "Tempo (BPM)",
            "type": "number",
            "minimum": 40,
            "maximum": 220,
            "default": 100
          },
          "swing": {
            "title": "Swing (%)",
            "description": "Delays every second step by this share of a step.",
            "type": "number",
            "minimum": 0,
            "maximum": 45,
            "default": 0
          },
          "repeats": {
            "title": "Repeats in the MIDI file",
            "type": "number",
            "minimum": 1,
            "maximum": 16,
            "default": 2
          }
        },
        "required": []
      },
      "TonegridPatternBuilderResult": {
        "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": [
              "project",
              "stats",
              "melodyRows",
              "drumRows",
              "midiFile",
              "projectFile",
              "openInApp"
            ],
            "properties": {
              "project": {
                "type": "object",
                "description": "Tonegrid project (format \"tonegrid\", version 3); see /project.schema.json on the Tonegrid host."
              },
              "stats": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "notes",
                  "drumHits",
                  "melodySteps",
                  "drumSteps",
                  "drumRows",
                  "cycleSteps",
                  "loopSeconds",
                  "songSeconds",
                  "lowestNote",
                  "highestNote"
                ],
                "properties": {
                  "notes": {
                    "type": "integer"
                  },
                  "drumHits": {
                    "type": "integer"
                  },
                  "melodySteps": {
                    "type": "integer"
                  },
                  "drumSteps": {
                    "type": "integer"
                  },
                  "drumRows": {
                    "type": "integer"
                  },
                  "cycleSteps": {
                    "type": "integer",
                    "description": "Steps until melody and drums line up again (and swing parity repeats)."
                  },
                  "loopSeconds": {
                    "type": "number"
                  },
                  "songSeconds": {
                    "type": "number"
                  },
                  "lowestNote": {
                    "type": "string"
                  },
                  "highestNote": {
                    "type": "string"
                  }
                }
              },
              "melodyRows": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "row",
                    "note",
                    "midi",
                    "hits"
                  ],
                  "properties": {
                    "row": {
                      "type": "integer"
                    },
                    "note": {
                      "type": "string"
                    },
                    "midi": {
                      "type": "integer"
                    },
                    "hits": {
                      "type": "integer"
                    }
                  }
                }
              },
              "drumRows": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "drum",
                    "midi",
                    "hits"
                  ],
                  "properties": {
                    "drum": {
                      "type": "string"
                    },
                    "midi": {
                      "type": "integer"
                    },
                    "hits": {
                      "type": "integer"
                    }
                  }
                }
              },
              "midiFile": {
                "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)."
                  }
                }
              },
              "projectFile": {
                "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)."
                  }
                }
              },
              "openInApp": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "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"
              }
            }
          }
        }
      },
      "TonegridScalePlannerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "root": {
            "title": "Root note (MIDI number)",
            "description": "48 is C3 and 60 is C4.",
            "type": "number",
            "minimum": 48,
            "maximum": 71,
            "default": 48
          },
          "scale": {
            "title": "Scale",
            "type": "string",
            "enum": [
              "pentatonic",
              "minorPentatonic",
              "major",
              "minor",
              "dorian",
              "mixolydian",
              "harmonicMinor",
              "blues",
              "chromatic"
            ],
            "default": "pentatonic"
          },
          "bpm": {
            "title": "Tempo (BPM)",
            "type": "number",
            "minimum": 40,
            "maximum": 220,
            "default": 120
          },
          "swing": {
            "title": "Swing (%)",
            "description": "Delays alternate steps, like the Tonegrid swing control.",
            "type": "number",
            "minimum": 0,
            "maximum": 45,
            "default": 0
          },
          "rows": {
            "title": "Melody rows",
            "type": "number",
            "minimum": 1,
            "maximum": 24,
            "default": 16
          },
          "steps": {
            "title": "Steps",
            "type": "number",
            "minimum": 1,
            "maximum": 32,
            "default": 16
          },
          "cells": {
            "title": "Selected cells: step,row",
            "description": "One pair per line, counted from 1. Row 1 is the highest pitch; the last row is the root. Leave blank for just the scale.",
            "type": "string",
            "maxLength": 10000,
            "default": "1,16\n5,14\n9,12\n13,11"
          }
        },
        "required": []
      },
      "TonegridScalePlannerResult": {
        "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": [
              "root",
              "scale",
              "bpm",
              "swing",
              "steps",
              "rows",
              "timePerStep",
              "loopSeconds",
              "notes",
              "events"
            ],
            "properties": {
              "root": {
                "type": "integer"
              },
              "scale": {
                "type": "string"
              },
              "bpm": {
                "type": "integer"
              },
              "swing": {
                "type": "integer"
              },
              "steps": {
                "type": "integer"
              },
              "rows": {
                "type": "integer"
              },
              "timePerStep": {
                "type": "number"
              },
              "loopSeconds": {
                "type": "number"
              },
              "notes": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "row",
                    "midi",
                    "note",
                    "frequencyHz"
                  ],
                  "properties": {
                    "row": {
                      "type": "integer"
                    },
                    "midi": {
                      "type": "integer"
                    },
                    "note": {
                      "type": "string"
                    },
                    "frequencyHz": {
                      "type": "number"
                    }
                  }
                }
              },
              "events": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "step",
                    "row",
                    "midi",
                    "note",
                    "frequencyHz",
                    "timeSeconds"
                  ],
                  "properties": {
                    "row": {
                      "type": "integer"
                    },
                    "midi": {
                      "type": "integer"
                    },
                    "note": {
                      "type": "string"
                    },
                    "frequencyHz": {
                      "type": "number"
                    },
                    "step": {
                      "type": "integer"
                    },
                    "timeSeconds": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "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"
              }
            }
          }
        }
      },
      "AudioConverterInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "file": {
            "title": "Audio file",
            "description": "MP3, WAV, FLAC, Ogg/Opus, M4A/AAC, AIFF or WMA (also the first audio track of a video), max 100 MB and 2 hours. Processed on our server in a private temporary folder that is deleted afterwards; the result can be downloaded for about 2 hours. Upload as {filename, contentType?, contentBase64}. Max 102400 KiB. Accepts .mp3,.wav,.flac,.ogg,.oga,.opus,.m4a,.aac,.aif,.aiff,.wma,.mp4,.mov,.webm,.mkv,audio/*.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "format": {
            "title": "Output format",
            "type": "string",
            "enum": [
              "mp3",
              "m4a",
              "ogg",
              "opus",
              "flac",
              "wav",
              "aiff"
            ],
            "default": "mp3"
          },
          "mode": {
            "title": "Bitrate mode (lossy formats)",
            "type": "string",
            "enum": [
              "cbr",
              "vbr"
            ],
            "default": "cbr"
          },
          "bitrate": {
            "title": "Bitrate",
            "description": "Used in fixed-bitrate mode. 128 kbit/s is fine for speech, 192–320 kbit/s for music.",
            "type": "string",
            "enum": [
              "32",
              "48",
              "64",
              "96",
              "128",
              "160",
              "192",
              "224",
              "256",
              "320"
            ],
            "default": "192"
          },
          "vbrQuality": {
            "title": "VBR quality",
            "type": "string",
            "enum": [
              "high",
              "medium",
              "low"
            ],
            "default": "high"
          },
          "sampleRate": {
            "title": "Sample rate",
            "type": "string",
            "enum": [
              "keep",
              "8000",
              "16000",
              "22050",
              "32000",
              "44100",
              "48000",
              "96000"
            ],
            "default": "keep"
          },
          "channels": {
            "title": "Channels",
            "type": "string",
            "enum": [
              "keep",
              "1",
              "2"
            ],
            "default": "keep"
          },
          "bitDepth": {
            "title": "Bit depth (WAV, FLAC, AIFF)",
            "type": "string",
            "enum": [
              "16",
              "24"
            ],
            "default": "16"
          },
          "keepTags": {
            "title": "Keep tags (title, artist, album …) and the album cover where the format supports it",
            "type": "boolean",
            "default": true
          }
        },
        "required": [
          "file"
        ]
      },
      "AudioConverterResult": {
        "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": [
              "inputContainer",
              "outputFormat",
              "encoderSetting",
              "sampleRate",
              "channels",
              "durationSeconds",
              "originalBytes",
              "outputBytes",
              "tagsKept",
              "coverKept",
              "before",
              "after",
              "artifact"
            ],
            "properties": {
              "inputContainer": {
                "type": "string"
              },
              "outputFormat": {
                "type": "string",
                "enum": [
                  "mp3",
                  "wav",
                  "flac",
                  "ogg",
                  "opus",
                  "m4a",
                  "aiff"
                ]
              },
              "encoderSetting": {
                "type": "string"
              },
              "sampleRate": {
                "type": "integer"
              },
              "channels": {
                "type": "integer"
              },
              "durationSeconds": {
                "type": "number"
              },
              "originalBytes": {
                "type": "integer"
              },
              "outputBytes": {
                "type": "integer"
              },
              "tagsKept": {
                "type": "boolean",
                "description": "True when every source tag was found in the result (format- or stream-level tags); false when tags were removed or the output format could not store some of them."
              },
              "tagsMissing": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Source tags that the output format could not store."
              },
              "outputSampleRate": {
                "type": "integer",
                "description": "Sample rate the finished file reports (ffprobe). Differs from sampleRate (the rate given to the encoder) for Opus, which always decodes at 48000 Hz."
              },
              "coverKept": {
                "type": "boolean"
              },
              "before": {
                "type": "object",
                "required": [
                  "container",
                  "durationSeconds",
                  "bytes",
                  "streams"
                ],
                "properties": {
                  "container": {
                    "type": "string"
                  },
                  "containerLabel": {
                    "type": "string",
                    "description": "Readable format name, e.g. MP4 instead of the demuxer family \"mov,mp4,m4a,3gp,3g2,mj2\"."
                  },
                  "durationSeconds": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "bitRateKbps": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "streams": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "tags": {
                    "type": "object"
                  }
                }
              },
              "after": {
                "type": "object",
                "required": [
                  "container",
                  "durationSeconds",
                  "bytes",
                  "streams"
                ],
                "properties": {
                  "container": {
                    "type": "string"
                  },
                  "containerLabel": {
                    "type": "string",
                    "description": "Readable format name, e.g. MP4 instead of the demuxer family \"mov,mp4,m4a,3gp,3g2,mj2\"."
                  },
                  "durationSeconds": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "bitRateKbps": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "streams": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "tags": {
                    "type": "object"
                  }
                }
              },
              "artifact": {
                "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"
              }
            }
          }
        }
      },
      "AudioCutterMergerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "mode": {
            "title": "What to do",
            "type": "string",
            "enum": [
              "cut",
              "merge"
            ],
            "default": "cut"
          },
          "file": {
            "title": "Audio file (clip 1)",
            "description": "MP3, WAV, FLAC, Ogg/Opus, M4A, AIFF; max 100 MB in total. Processed on our server in a private temporary folder that is deleted afterwards; the result can be downloaded for about 2 hours. Upload as {filename, contentType?, contentBase64}. Max 102400 KiB. Accepts .mp3,.wav,.flac,.ogg,.oga,.opus,.m4a,.aac,.aif,.aiff,.wma,.mp4,.mov,.webm,.mkv,audio/*.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "file2": {
            "title": "Clip 2 (merge mode)",
            "type": "object",
            "description": "Upload as {filename, contentType?, contentBase64}. Max 102400 KiB. Accepts .mp3,.wav,.flac,.ogg,.oga,.opus,.m4a,.aac,.aif,.aiff,.wma,.mp4,.mov,.webm,.mkv,audio/*.",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "file3": {
            "title": "Clip 3 (merge mode)",
            "type": "object",
            "description": "Upload as {filename, contentType?, contentBase64}. Max 102400 KiB. Accepts .mp3,.wav,.flac,.ogg,.oga,.opus,.m4a,.aac,.aif,.aiff,.wma,.mp4,.mov,.webm,.mkv,audio/*.",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "file4": {
            "title": "Clip 4 (merge mode)",
            "type": "object",
            "description": "Upload as {filename, contentType?, contentBase64}. Max 102400 KiB. Accepts .mp3,.wav,.flac,.ogg,.oga,.opus,.m4a,.aac,.aif,.aiff,.wma,.mp4,.mov,.webm,.mkv,audio/*.",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "file5": {
            "title": "Clip 5 (merge mode)",
            "type": "object",
            "description": "Upload as {filename, contentType?, contentBase64}. Max 102400 KiB. Accepts .mp3,.wav,.flac,.ogg,.oga,.opus,.m4a,.aac,.aif,.aiff,.wma,.mp4,.mov,.webm,.mkv,audio/*.",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "file6": {
            "title": "Clip 6 (merge mode)",
            "type": "object",
            "description": "Upload as {filename, contentType?, contentBase64}. Max 102400 KiB. Accepts .mp3,.wav,.flac,.ogg,.oga,.opus,.m4a,.aac,.aif,.aiff,.wma,.mp4,.mov,.webm,.mkv,audio/*.",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "file7": {
            "title": "Clip 7 (merge mode)",
            "type": "object",
            "description": "Upload as {filename, contentType?, contentBase64}. Max 102400 KiB. Accepts .mp3,.wav,.flac,.ogg,.oga,.opus,.m4a,.aac,.aif,.aiff,.wma,.mp4,.mov,.webm,.mkv,audio/*.",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "file8": {
            "title": "Clip 8 (merge mode)",
            "type": "object",
            "description": "Upload as {filename, contentType?, contentBase64}. Max 102400 KiB. Accepts .mp3,.wav,.flac,.ogg,.oga,.opus,.m4a,.aac,.aif,.aiff,.wma,.mp4,.mov,.webm,.mkv,audio/*.",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "file9": {
            "title": "Clip 9 (merge mode)",
            "type": "object",
            "description": "Upload as {filename, contentType?, contentBase64}. Max 102400 KiB. Accepts .mp3,.wav,.flac,.ogg,.oga,.opus,.m4a,.aac,.aif,.aiff,.wma,.mp4,.mov,.webm,.mkv,audio/*.",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "file10": {
            "title": "Clip 10 (merge mode)",
            "type": "object",
            "description": "Upload as {filename, contentType?, contentBase64}. Max 102400 KiB. Accepts .mp3,.wav,.flac,.ogg,.oga,.opus,.m4a,.aac,.aif,.aiff,.wma,.mp4,.mov,.webm,.mkv,audio/*.",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "start": {
            "title": "Start time (cut mode)",
            "description": "Seconds (5.5), mm:ss (1:30) or hh:mm:ss.mmm. Empty = from the beginning.",
            "type": "string",
            "maxLength": 20
          },
          "end": {
            "title": "End time (cut mode)",
            "description": "Empty = to the end.",
            "type": "string",
            "maxLength": 20
          },
          "ranges": {
            "title": "Time ranges (cut mode, optional)",
            "description": "One range per line, start-end. Overrides start/end.",
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 50
          },
          "rangeAction": {
            "title": "Ranges are",
            "type": "string",
            "enum": [
              "keep",
              "remove"
            ],
            "default": "keep"
          },
          "fadeIn": {
            "title": "Fade in",
            "type": "number",
            "minimum": 0,
            "maximum": 30,
            "default": 0,
            "x-unit": "s"
          },
          "fadeOut": {
            "title": "Fade out",
            "type": "number",
            "minimum": 0,
            "maximum": 30,
            "default": 0,
            "x-unit": "s"
          },
          "crossfade": {
            "title": "Crossfade between clips (merge mode)",
            "type": "number",
            "minimum": 0,
            "maximum": 10,
            "default": 0,
            "x-unit": "s"
          },
          "format": {
            "title": "Output format",
            "type": "string",
            "enum": [
              "mp3",
              "m4a",
              "ogg",
              "opus",
              "flac",
              "wav"
            ],
            "default": "mp3"
          },
          "bitrate": {
            "title": "Bitrate (lossy formats)",
            "type": "string",
            "enum": [
              "32",
              "48",
              "64",
              "96",
              "128",
              "160",
              "192",
              "224",
              "256",
              "320"
            ],
            "default": "192"
          }
        },
        "required": [
          "file"
        ]
      },
      "AudioCutterMergerResult": {
        "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": [
              "mode",
              "segments",
              "clips",
              "outputFormat",
              "expectedSeconds",
              "durationSeconds",
              "outputBytes",
              "artifact"
            ],
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "cut",
                  "merge"
                ]
              },
              "segments": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "start",
                    "end"
                  ],
                  "properties": {
                    "start": {
                      "type": "number"
                    },
                    "end": {
                      "type": "number"
                    }
                  }
                },
                "description": "Kept parts of the source (cut mode)."
              },
              "clips": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "clip",
                    "durationSeconds",
                    "codec"
                  ],
                  "properties": {
                    "clip": {
                      "type": "integer"
                    },
                    "durationSeconds": {
                      "type": "number"
                    },
                    "codec": {
                      "type": "string"
                    }
                  }
                }
              },
              "crossfadeSeconds": {
                "type": "number"
              },
              "outputFormat": {
                "type": "string"
              },
              "expectedSeconds": {
                "type": "number"
              },
              "durationSeconds": {
                "type": "number"
              },
              "outputBytes": {
                "type": "integer"
              },
              "artifact": {
                "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"
              }
            }
          }
        }
      },
      "AudioLoudnessNormalizerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "file": {
            "title": "Audio file",
            "description": "MP3, WAV, FLAC, Ogg/Opus, M4A, AIFF; max 100 MB and 45 minutes. Processed on our server in a private temporary folder that is deleted afterwards; the result can be downloaded for about 2 hours. Upload as {filename, contentType?, contentBase64}. Max 102400 KiB. Accepts .mp3,.wav,.flac,.ogg,.oga,.opus,.m4a,.aac,.aif,.aiff,.wma,.mp4,.mov,.webm,.mkv,audio/*.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "preset": {
            "title": "Target",
            "type": "string",
            "enum": [
              "spotify",
              "youtube",
              "apple",
              "podcast-stereo",
              "podcast-mono",
              "ebu",
              "custom"
            ],
            "default": "spotify"
          },
          "targetLufs": {
            "title": "Custom integrated loudness",
            "description": "Only used with \"Custom target\".",
            "type": "number",
            "minimum": -40,
            "maximum": -5,
            "default": -16,
            "x-unit": "LUFS"
          },
          "truePeak": {
            "title": "Custom true-peak limit",
            "description": "Only used with \"Custom target\".",
            "type": "number",
            "minimum": -9,
            "maximum": 0,
            "default": -1,
            "x-unit": "dBTP"
          },
          "lra": {
            "title": "Loudness range target",
            "description": "Higher keeps more dynamics. If the source range is larger, ffmpeg compresses it.",
            "type": "number",
            "minimum": 1,
            "maximum": 50,
            "default": 11,
            "x-unit": "LU"
          },
          "format": {
            "title": "Output format",
            "type": "string",
            "enum": [
              "same",
              "mp3",
              "m4a",
              "ogg",
              "opus",
              "flac",
              "wav"
            ],
            "default": "same"
          },
          "bitrate": {
            "title": "Bitrate (lossy formats)",
            "type": "string",
            "enum": [
              "32",
              "48",
              "64",
              "96",
              "128",
              "160",
              "192",
              "224",
              "256",
              "320"
            ],
            "default": "192"
          }
        },
        "required": [
          "file"
        ]
      },
      "AudioLoudnessNormalizerResult": {
        "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": [
              "preset",
              "target",
              "before",
              "after",
              "normalizationType",
              "afterSource",
              "gainDb",
              "outputFormat",
              "sampleRate",
              "channels",
              "durationSeconds",
              "outputBytes",
              "artifact"
            ],
            "properties": {
              "preset": {
                "type": "string"
              },
              "target": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "integratedLufs",
                  "truePeakDbtp",
                  "lra"
                ],
                "properties": {
                  "integratedLufs": {
                    "type": "number"
                  },
                  "truePeakDbtp": {
                    "type": "number"
                  },
                  "lra": {
                    "type": "number"
                  }
                }
              },
              "before": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "integratedLufs",
                  "truePeakDbtp",
                  "lra",
                  "thresholdLufs"
                ],
                "properties": {
                  "integratedLufs": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "truePeakDbtp": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "lra": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "thresholdLufs": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                }
              },
              "after": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "integratedLufs",
                  "truePeakDbtp",
                  "lra",
                  "thresholdLufs"
                ],
                "properties": {
                  "integratedLufs": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "truePeakDbtp": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "lra": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "thresholdLufs": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                }
              },
              "normalizationType": {
                "type": "string",
                "description": "linear = one gain change; dynamic = loudnorm also compressed/limited the audio."
              },
              "afterSource": {
                "type": "string",
                "enum": [
                  "measured",
                  "loudnorm-report"
                ],
                "description": "measured = the encoded file was measured again (files up to 15 min)."
              },
              "gainDb": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "outputFormat": {
                "type": "string"
              },
              "sampleRate": {
                "type": "integer"
              },
              "outputSampleRate": {
                "type": "integer",
                "description": "Sample rate the finished file reports (Opus always 48000)."
              },
              "channels": {
                "type": "integer"
              },
              "durationSeconds": {
                "type": "number"
              },
              "outputBytes": {
                "type": "integer"
              },
              "artifact": {
                "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"
              }
            }
          }
        }
      }
    }
  }
}