{
  "schemaVersion": 1,
  "slug": "music-key-finder",
  "category": "music",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "262854a2c9fb0635e280c187e057fc99c24c0035f2c052e86112e1c9d4c0ed86",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/music-key-finder.json",
    "src/engine.js"
  ],
  "runtimeSource": "/sources/engine.txt",
  "runtimeHash": "5c888d8898afbdee36267bb9977829e1e2ac568f6677c8ee87469c139864645c",
  "scope": "The compute function records implemented equations, constants, lookup tables and custom rules. Inputs record defaults and allowed values; outputs record precision and units. Defaults are examples, not recommendations. General background references are not evidence for this specific algorithm.",
  "publishedFormula": {
    "formula": "N = {n_1,…,n_m}; S_r = {(r+k) mod 12 : k ∈ {0,2,4,5,7,9,11}}; hits(r) = |{j ∈ {1,…,m} : n_j ∈ S_r}|; score(r) = hits(r) + {0.5, r ∈ N; 0, r ∉ N}; key = argmax_{r ∈ {0,…,11}} score(r)",
    "vars": [
      [
        "r",
        "candidate root pitch class 0–11"
      ],
      [
        "n_j",
        "pitch class of the j-th input note"
      ],
      [
        "m",
        "number of input notes"
      ],
      [
        "N",
        "set of distinct input pitch classes"
      ],
      [
        "S_r",
        "major-scale pitch-class set for root r"
      ],
      [
        "hits",
        "input notes inside scale"
      ],
      [
        "score",
        "match score for root"
      ]
    ],
    "note": "Accepts 1–64 whitespace-, comma-, or semicolon-separated note names A–G with optional # or b; malformed/empty lists are rejected. Scores all 12 major keys by matching entered pitch classes plus a small tonic bonus. A high score is a heuristic, not a tonal analysis."
  },
  "externalEvidence": null,
  "contract": {
    "compute": "const sharps = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; const flats = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']; const names = accidentals === 'flat' ? flats : sharps; const norm = (n) => { const i = sharps.indexOf(n); return i >= 0 ? i : flats.indexOf(n); }; const raw=String(notes).trim().split(/[\\s,;]+/).filter(Boolean);if(!raw.length||raw.length>64||raw.some(n=>norm(n)<0))return{key:null,notesInScale:null,confidence:null,relativeMinor:null,scaleNotes:null,allKeysRanked:null,__error:'inputError'};const input=raw.map(norm); const major = [0, 2, 4, 5, 7, 9, 11]; const results = []; for (let root = 0; root < 12; root++) { const scale = major.map((s) => (root + s) % 12); let hits = 0; for (const n of input) if (scale.indexOf(n) >= 0) hits++; const rootBonus = input.indexOf(root) >= 0 ? 1 : 0; results.push({ root: root, hits: hits, score: hits + rootBonus*0.5 }); } results.sort((a, b) => b.score - a.score); const best = results[0]; const scale = major.map((s) => names[(best.root + s) % 12]); const minorRoot = (best.root + 9) % 12; return { key: names[best.root] + ' major', notesInScale: best.hits, confidence: input.length > 0 ? best.hits/input.length*100 : 0, relativeMinor: names[minorRoot] + ' minor', scaleNotes: scale.join(' '), allKeysRanked: results.slice(1, 4).map((r) => names[r.root] + ' (' + r.hits + ')').join(', ') };",
    "inputs": [
      {
        "name": "notes",
        "type": "text",
        "default": "C D E F G A",
        "step": 1
      },
      {
        "name": "accidentals",
        "type": "select",
        "default": "sharp",
        "options": [
          {
            "value": "sharp"
          },
          {
            "value": "flat"
          }
        ]
      }
    ],
    "outputs": [
      {
        "name": "key",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "notesInScale",
        "precision": 0,
        "unit": ""
      },
      {
        "name": "confidence",
        "precision": 0,
        "unit": "%"
      },
      {
        "name": "relativeMinor",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "scaleNotes",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "allKeysRanked",
        "precision": 0,
        "unit": "",
        "type": "text"
      }
    ],
    "interpret": null
  }
}
