Problem

Hooktheory’s TheoryTab data encodes chords as compact JSON, not as voicings, scale-degree analysis, or something you can practice against. I wanted ear training on real songs: reconstruct those chords correctly, then play and quiz them in the browser and on phones.

Approach

Acquiring is a monorepo with three native applications that share catalog and behavioral contracts, not runtime code:

  • Web: JavaScript, HTML, CSS, and Tone.js
  • Android: Kotlin and Jetpack Compose
  • iOS: Swift and SwiftUI

A chord interpreter turns TheoryTab JSON into piano voicings, scale degrees, and Roman numeral symbols. A closed-loop oracle scrapes Hooktheory ground truth and scores the engine per chord instead of relying on manual spot checks.

Catalog tooling harvests that data into a gzip-compressed SQLite database with a published minimum of about 41,000 browseable songs. The archive is a replaceable release artifact. User data such as playlists lives in a separate store so a catalog swap cannot erase it. Bulky catalog, playback, and harvest files stay outside Git.

The quiz plays melody and chords from a chosen song section, with tempo, transposition, instrument, and arpeggiation controls. Android also captures pitch through the microphone for sing-back and interval practice. iOS is being brought to the same feature inventory against those shared contracts.

Result

The web player and Android app run against the full catalog: search and browse, reconstructed voicings, and a quiz loop on real progressions. iOS implements the same catalog and quiz contracts and is still catching up to Android’s completed surface.

Reflection

Keeping three native codebases honest through contracts and fixtures, rather than one shared UI layer, made platform-specific audio and persistence straightforward, at the cost of reimplementing the same behavior three times. Parking the catalog outside Git kept the repository cloneable; it also means a documented data-root setup is part of making the product run at all.