{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://audio.raphaelsalaja.com/schemas/patch.schema.json",
  "title": "@web-kits/audio Sound Patch",
  "description": "Schema for @web-kits/audio sound patch JSON files",
  "type": "object",
  "required": ["name", "sounds"],
  "properties": {
    "$schema": {
      "type": "string"
    },
    "name": {
      "type": "string",
      "description": "Display name of the patch"
    },
    "author": {
      "type": "string"
    },
    "version": {
      "type": "string"
    },
    "description": {
      "type": "string"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "license": {
      "type": "string",
      "description": "SPDX license identifier (e.g. MIT, Apache-2.0)"
    },
    "compatibility": {
      "type": "string",
      "description": "Semver range for @web-kits/audio compatibility (e.g. >=1.0.0)"
    },
    "sounds": {
      "type": "object",
      "description": "Map of sound names to their definitions",
      "additionalProperties": {
        "$ref": "#/$defs/SoundDefinition"
      }
    }
  },
  "additionalProperties": false,
  "$defs": {
    "SoundDefinition": {
      "oneOf": [
        {
          "$ref": "#/$defs/Layer"
        },
        {
          "$ref": "#/$defs/MultiLayerSound"
        }
      ]
    },
    "MultiLayerSound": {
      "type": "object",
      "required": ["layers"],
      "properties": {
        "layers": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Layer"
          }
        },
        "effects": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Effect"
          }
        }
      },
      "additionalProperties": false
    },
    "Layer": {
      "type": "object",
      "required": ["source"],
      "properties": {
        "source": {
          "$ref": "#/$defs/Source"
        },
        "filter": {
          "oneOf": [
            {
              "$ref": "#/$defs/Filter"
            },
            {
              "type": "array",
              "items": {
                "$ref": "#/$defs/Filter"
              }
            }
          ]
        },
        "envelope": {
          "$ref": "#/$defs/Envelope"
        },
        "gain": {
          "type": "number"
        },
        "pan": {
          "type": "number",
          "minimum": -1,
          "maximum": 1
        },
        "panner": {
          "$ref": "#/$defs/Panner3D"
        },
        "delay": {
          "type": "number",
          "minimum": 0
        },
        "lfo": {
          "oneOf": [
            {
              "$ref": "#/$defs/LFO"
            },
            {
              "type": "array",
              "items": {
                "$ref": "#/$defs/LFO"
              }
            }
          ]
        },
        "effects": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Effect"
          }
        }
      },
      "additionalProperties": false
    },
    "Source": {
      "oneOf": [
        {
          "$ref": "#/$defs/OscillatorSource"
        },
        {
          "$ref": "#/$defs/NoiseSource"
        },
        {
          "$ref": "#/$defs/WavetableSource"
        },
        {
          "$ref": "#/$defs/SampleSource"
        },
        {
          "$ref": "#/$defs/ConstantSource"
        }
      ]
    },
    "OscillatorSource": {
      "type": "object",
      "required": ["type", "frequency"],
      "properties": {
        "type": {
          "enum": ["sine", "triangle", "square", "sawtooth"]
        },
        "frequency": {
          "oneOf": [
            {
              "type": "number"
            },
            {
              "type": "object",
              "required": ["start", "end"],
              "properties": {
                "start": {
                  "type": "number"
                },
                "end": {
                  "type": "number"
                }
              },
              "additionalProperties": false
            }
          ]
        },
        "detune": {
          "type": "number"
        },
        "fm": {
          "type": "object",
          "required": ["ratio", "depth"],
          "properties": {
            "ratio": {
              "type": "number"
            },
            "depth": {
              "type": "number"
            }
          },
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    },
    "NoiseSource": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "const": "noise"
        },
        "color": {
          "enum": ["white", "pink", "brown"]
        }
      },
      "additionalProperties": false
    },
    "WavetableSource": {
      "type": "object",
      "required": ["type", "harmonics", "frequency"],
      "properties": {
        "type": {
          "const": "wavetable"
        },
        "harmonics": {
          "type": "array",
          "items": {
            "type": "number"
          }
        },
        "frequency": {
          "oneOf": [
            {
              "type": "number"
            },
            {
              "type": "object",
              "required": ["start", "end"],
              "properties": {
                "start": {
                  "type": "number"
                },
                "end": {
                  "type": "number"
                }
              },
              "additionalProperties": false
            }
          ]
        }
      },
      "additionalProperties": false
    },
    "SampleSource": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "const": "sample"
        },
        "url": {
          "type": "string"
        },
        "playbackRate": {
          "type": "number"
        },
        "detune": {
          "type": "number"
        },
        "loop": {
          "type": "boolean"
        },
        "loopStart": {
          "type": "number"
        },
        "loopEnd": {
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "ConstantSource": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "const": "constant"
        },
        "offset": {
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "Filter": {
      "oneOf": [
        {
          "$ref": "#/$defs/BiquadFilter"
        },
        {
          "$ref": "#/$defs/IIRFilter"
        }
      ]
    },
    "BiquadFilter": {
      "type": "object",
      "required": ["type", "frequency"],
      "properties": {
        "type": {
          "enum": [
            "lowpass",
            "highpass",
            "bandpass",
            "notch",
            "allpass",
            "peaking",
            "lowshelf",
            "highshelf"
          ]
        },
        "frequency": {
          "type": "number"
        },
        "resonance": {
          "type": "number"
        },
        "gain": {
          "type": "number"
        },
        "envelope": {
          "$ref": "#/$defs/FilterEnvelope"
        }
      },
      "additionalProperties": false
    },
    "FilterEnvelope": {
      "type": "object",
      "required": ["peak", "decay"],
      "properties": {
        "attack": {
          "type": "number",
          "minimum": 0
        },
        "peak": {
          "type": "number"
        },
        "decay": {
          "type": "number",
          "minimum": 0
        }
      },
      "additionalProperties": false
    },
    "IIRFilter": {
      "type": "object",
      "required": ["type", "feedforward", "feedback"],
      "properties": {
        "type": {
          "const": "iir"
        },
        "feedforward": {
          "type": "array",
          "items": {
            "type": "number"
          }
        },
        "feedback": {
          "type": "array",
          "items": {
            "type": "number"
          }
        }
      },
      "additionalProperties": false
    },
    "Envelope": {
      "type": "object",
      "required": ["decay"],
      "properties": {
        "attack": {
          "type": "number",
          "minimum": 0
        },
        "decay": {
          "type": "number",
          "minimum": 0
        },
        "sustain": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "release": {
          "type": "number",
          "minimum": 0
        }
      },
      "additionalProperties": false
    },
    "LFO": {
      "type": "object",
      "required": ["type", "frequency", "depth", "target"],
      "properties": {
        "type": {
          "enum": ["sine", "triangle", "square", "sawtooth"]
        },
        "frequency": {
          "type": "number"
        },
        "depth": {
          "type": "number"
        },
        "target": {
          "enum": [
            "frequency",
            "detune",
            "gain",
            "pan",
            "filter.frequency",
            "filter.detune",
            "filter.Q",
            "filter.gain",
            "playbackRate"
          ]
        }
      },
      "additionalProperties": false
    },
    "Panner3D": {
      "type": "object",
      "required": ["positionX", "positionY", "positionZ"],
      "properties": {
        "positionX": {
          "type": "number"
        },
        "positionY": {
          "type": "number"
        },
        "positionZ": {
          "type": "number"
        },
        "orientationX": {
          "type": "number"
        },
        "orientationY": {
          "type": "number"
        },
        "orientationZ": {
          "type": "number"
        },
        "panningModel": {
          "enum": ["equalpower", "HRTF"]
        },
        "distanceModel": {
          "enum": ["linear", "inverse", "exponential"]
        },
        "maxDistance": {
          "type": "number"
        },
        "refDistance": {
          "type": "number"
        },
        "rolloffFactor": {
          "type": "number"
        },
        "coneInnerAngle": {
          "type": "number"
        },
        "coneOuterAngle": {
          "type": "number"
        },
        "coneOuterGain": {
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "Effect": {
      "oneOf": [
        {
          "$ref": "#/$defs/ReverbEffect"
        },
        {
          "$ref": "#/$defs/ConvolverEffect"
        },
        {
          "$ref": "#/$defs/DelayEffect"
        },
        {
          "$ref": "#/$defs/DistortionEffect"
        },
        {
          "$ref": "#/$defs/ChorusEffect"
        },
        {
          "$ref": "#/$defs/FlangerEffect"
        },
        {
          "$ref": "#/$defs/PhaserEffect"
        },
        {
          "$ref": "#/$defs/TremoloEffect"
        },
        {
          "$ref": "#/$defs/VibratoEffect"
        },
        {
          "$ref": "#/$defs/BitcrusherEffect"
        },
        {
          "$ref": "#/$defs/CompressorEffect"
        },
        {
          "$ref": "#/$defs/EQEffect"
        },
        {
          "$ref": "#/$defs/GainEffect"
        },
        {
          "$ref": "#/$defs/StereoPanEffect"
        }
      ]
    },
    "ReverbEffect": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "const": "reverb"
        },
        "decay": {
          "type": "number",
          "minimum": 0
        },
        "preDelay": {
          "type": "number",
          "minimum": 0
        },
        "damping": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "roomSize": {
          "type": "number",
          "minimum": 0
        },
        "mix": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        }
      },
      "additionalProperties": false
    },
    "ConvolverEffect": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "const": "convolver"
        },
        "url": {
          "type": "string"
        },
        "mix": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        }
      },
      "additionalProperties": false
    },
    "DelayEffect": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "const": "delay"
        },
        "time": {
          "type": "number",
          "minimum": 0
        },
        "feedback": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "feedbackFilter": {
          "type": "object",
          "required": ["type", "frequency"],
          "properties": {
            "type": {
              "enum": [
                "lowpass",
                "highpass",
                "bandpass",
                "notch",
                "allpass",
                "peaking",
                "lowshelf",
                "highshelf"
              ]
            },
            "frequency": {
              "type": "number"
            },
            "Q": {
              "type": "number"
            }
          },
          "additionalProperties": false
        },
        "mix": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        }
      },
      "additionalProperties": false
    },
    "DistortionEffect": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "const": "distortion"
        },
        "amount": {
          "type": "number"
        },
        "mix": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        }
      },
      "additionalProperties": false
    },
    "ChorusEffect": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "const": "chorus"
        },
        "rate": {
          "type": "number"
        },
        "depth": {
          "type": "number"
        },
        "mix": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        }
      },
      "additionalProperties": false
    },
    "FlangerEffect": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "const": "flanger"
        },
        "rate": {
          "type": "number"
        },
        "depth": {
          "type": "number"
        },
        "feedback": {
          "type": "number"
        },
        "mix": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        }
      },
      "additionalProperties": false
    },
    "PhaserEffect": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "const": "phaser"
        },
        "rate": {
          "type": "number"
        },
        "depth": {
          "type": "number"
        },
        "stages": {
          "type": "integer",
          "minimum": 1
        },
        "feedback": {
          "type": "number"
        },
        "mix": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        }
      },
      "additionalProperties": false
    },
    "TremoloEffect": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "const": "tremolo"
        },
        "rate": {
          "type": "number"
        },
        "depth": {
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "VibratoEffect": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "const": "vibrato"
        },
        "rate": {
          "type": "number"
        },
        "depth": {
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "BitcrusherEffect": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "const": "bitcrusher"
        },
        "bits": {
          "type": "integer",
          "minimum": 1,
          "maximum": 16
        },
        "sampleRateReduction": {
          "type": "number",
          "minimum": 1
        },
        "mix": {
          "type": "number",
          "minimum": 0,
          "maximum": 1
        }
      },
      "additionalProperties": false
    },
    "CompressorEffect": {
      "type": "object",
      "required": ["type"],
      "properties": {
        "type": {
          "const": "compressor"
        },
        "threshold": {
          "type": "number"
        },
        "knee": {
          "type": "number"
        },
        "ratio": {
          "type": "number"
        },
        "attack": {
          "type": "number"
        },
        "release": {
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "EQEffect": {
      "type": "object",
      "required": ["type", "bands"],
      "properties": {
        "type": {
          "const": "eq"
        },
        "bands": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/EQBand"
          }
        }
      },
      "additionalProperties": false
    },
    "EQBand": {
      "type": "object",
      "required": ["type", "frequency", "gain"],
      "properties": {
        "type": {
          "enum": ["lowshelf", "highshelf", "peaking"]
        },
        "frequency": {
          "type": "number"
        },
        "gain": {
          "type": "number"
        },
        "Q": {
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "GainEffect": {
      "type": "object",
      "required": ["type", "value"],
      "properties": {
        "type": {
          "const": "gain"
        },
        "value": {
          "type": "number"
        }
      },
      "additionalProperties": false
    },
    "StereoPanEffect": {
      "type": "object",
      "required": ["type", "value"],
      "properties": {
        "type": {
          "const": "pan"
        },
        "value": {
          "type": "number",
          "minimum": -1,
          "maximum": 1
        }
      },
      "additionalProperties": false
    }
  }
}
