Algebraic structures for music theory, inspired by Oscar.jl.
Where Oscar combines GAP (groups), Polymake (geometry), Antic (number theory), and Singular (commutative algebra) for abstract algebra, Flux adapts these pillars to music:
| Oscar.jl | Flux Algebra | Domain |
|---|---|---|
| GAP — Groups | groups.py |
T/I group, PLR group, permutations |
| Antic — Number Theory | fields.py |
Tuning fields, algebraic tones |
| Singular — Rings | rings.py |
Harmonic rings, chord ideals |
| Polymake — Geometry | geometry.py |
Dial polytopes, voice-leading geodesics |
| Combinatorics | combinatorics.py |
Minimal voice leading |
| TropicalGeometry | tropical.py |
Tropical harmony, min-plus voice leading |
| Modules | modules.py |
Voice modules over harmonic rings |
| Serialization | serialization.py |
Save/load algebraic structures |
pip install flux-algebrafrom flux_algebra import HarmonicRing
hr = HarmonicRing(modulus=12)
# Pitch-class arithmetic
print(hr.add(7, 5)) # 0 (G + E = C)
print(hr.multiply(3, 4)) # 0 (M3 × M3 = tritone wraps to unison in Z/12)
# Ideals of Z/12Z correspond to musical structures
for ideal in hr.all_ideals():
print(ideal)
# {0} — unison
# {0, 6} — tritone
# {0, 4, 8} — augmented triad
# {0, 3, 6, 9} — diminished 7th
# {0, 2, 4, 6, 8, 10} — whole-tone scale
# Z/12Z — chromatic
# Chord ideals
cmaj = hr.chord_ideal([0, 4, 7])
print(cmaj) # Ideal([0, 4, 7])
print(cmaj.cosets()) # quotient ring representativesfrom flux_algebra import PLRGroup, Triad
plr = PLRGroup()
# Start from C major
c_major = Triad(root=0, quality="major") # {0, 4, 7}
# Parallel: C major → C minor
c_minor = plr.P(c_major)
print(c_minor) # Triad(0, minor) = {0, 3, 7}
# Leading-tone: C minor → E♭ major
eb_major = plr.L(c_minor)
print(eb_major) # Triad(3, major) = {3, 7, 10}
# Relative: E♭ major → C minor (back)
back = plr.R(eb_major)
print(back) # Triad(0, minor) ✓
# PLR walk: explore all 24 major/minor triads
walk = plr.walk("PLR", steps=3, start=c_major)
print([str(t) for t in walk])from flux_algebra.combinatorics import minimal_voice_leading, smoothness
source = [0, 4, 7] # C major
target = [5, 9, 0] # F major
vl = minimal_voice_leading(source, target)
print(vl) # [(0, 0), (4, 5), (7, 9)]
print(smoothness(vl)) # 7 semitones total movementfrom flux_algebra.geometry import DialPolytope, TraditionRegion
# Define tradition centers in [0,5]^3 dial space
jazz = TraditionRegion("jazz", center=(3.5, 2.0, 4.0), radius=0.8)
blues = TraditionRegion("blues", center=(4.0, 1.5, 3.0), radius=0.7)
classical = TraditionRegion("classical", center=(1.0, 4.5, 2.0), radius=1.0)
hull = DialPolytope(traditions=[jazz, blues, classical])
print(hull.volume()) # Volume of convex hull
print(hull.contains((2.5, 3.0, 3.0))) # Is this point explored?from flux_algebra.tropical import TropicalHarmony
th = TropicalHarmony()
# Tropical polynomial: min(x, x+4, x+7) models C major as a cost landscape
cost = th.chord_cost([0, 4, 7])
print(cost(0)) # 0 — C is a root, minimal cost
print(cost(3)) # 3 — E♭ is 3 semitones from nearest chord tone
# Tropical voice leading minimizes sum of movements
vl = th.tropical_voice_leading([0, 4, 7], [5, 9, 0])
print(vl)Every module mirrors a component of Oscar.jl, adapted for music-theoretic objects:
rings.py—HarmonicRing(ℤ/nℤ for pitch classes),IntervalRing(just intonation ratios),ChordIdeal(ideals generated by chord tones)fields.py—TuningField(field extensions for ET, meantone, just, Pythagorean),AlgebraicTone(tones as algebraic numbers)groups.py—TranspositionInversionGroup(T/I group ≅ D₂₄),PLRGroup(neo-Riemannian P/L/R),PermutationVoiceLeadinggeometry.py—DialPolytope(convex hull of tradition regions),VoiceLeadingGeodesic,TraditionRegioncombinatorics.py— Voice leading via Hungarian algorithm, smoothness/efficiency metricstropical.py— Min-plus semiring applied to harmony and voice leadingmodules.py— Free modules of voices over harmonic ringsserialization.py— JSON save/load for all algebraic structuresoscar_compat.py— Oscar.jl-style API surface for Julia interop
MIT