Welcome to disig, a Python package for exploring the tonal geometry of discrete periodic audio signals.
Inspired by Eulerβs original Tonnetz and its modern reinterpretations, disig extends these structures to the digital domainβwhere time is sampled, frequencies are modular, and multiplication replaces dilation.
At the core of this project is a categorical and signal-theoretic perspective on harmony, treating musical intervals as modular rescalings and organizing them into richly structured networks. These discrete tonnetze visualize the harmonic motion between audio signals under arithmetic transformations, revealing patterns that echo deep number-theoretic symmetries.
The underlying structure is a manifestation of a category of representations, where signals transform functorially under modular arithmetic operations. Tonnetz are diagrams of morphisms in this category that encode how spectral content behaves under group actions. This representation-theoretic framing situates tonal motion within a broader categorical picture: musical intervals and harmonic movement, and larger tonal structures all emerge from group actions on spectral data.
The library includes:
- Tools for generating and analyzing Tonnetz diagrams over arbitrary moduli
- Visualizers for arithmetic and geometric clusters in signal space
- Audio synthesis utilities for testing tonal structures directly via WAV playback
Whether you're a theorist, signal processing researcher, or just curious about how number theory meets timbre, disig provides an experimental playground for navigating the space of harmonic motion in modular time.
- Write
total_to_wavfunction in'./src/dissig/io/print_wav.py' - Do experiments with "major seventh chords," interpreted as squares generated by the ultipliers
$3$ and$5$ - Add Β§ to README about
dissigstools for evolving discrete signals along discrete tonnetze - Fix 0's in divisor grid images in
README.md - Analyze FT and STFT of step-realization of discrete audio signals
- 1.1 Tonnetze for continuous signals
- 1.1.1 Euler's tonnetz
- 1.1.2 Modern tonnetze
- 1.2 Tonality for discrete audio signals
- 1.3 Large-scale structure of discrete tonnetze
A tonnetz (German for "tone network," with plural tonnetze) is a type of diagram that depicts the intervalic inte-relationship between a collection of pitches, pitch classes, or even chords. One of the first known examples of a tonnetz is a drawing that the mathematician Leonard Euler included in a 1739 treatise on music theory.
Euler's tonnetz
Today, we tend to depict this same tonnetze as a grid network. This tonnetz depicts the relationship between the 12 pitch classes in a 12-tone equaltempered tuning when we move along the two important diatonic intervals P5 (a perfect fifth) and M3 (m major third):
A modernized version of Euler's tonnetz.
Here, we depict the tonnetz as a grid network, instead of Euler's original system of cascading brackets, but the content is essentially identical. We've added dotted arrows along the bottom edges of the diagram to indicate how it loops back along itself along P5 and M3 intervals.
The diagram is interesting from a music theoretical perspecitive because to exhibits lots of important diatonic-based musical phenomena in striking and often quite suggestive geometric patterns. To give just one example, all major and minor triads appear in this tonnetz as span or cospan diagrams:
Major and minor triads appear in our tonnetz as wedges (β§) and vees (β¨), respectively
Tonnetze became an important tool to developments in (musical) set theory and in neo-Riemann theory. For exemplary use of tonnetz in musical analysis, see:
- Dmitri Tymoczko. A Geometry of Music: Harmony and Counterpoint in the Extended Common Practice. Oxford Studies in Music Theory. Oxford University Press, March 2011. 480 pages.
- Richard Cohn. Audacious Euphony: Chromatic Harmony and the Triadβs Second Nature. Oxford Studies in Music Theory. Oxford University Press, January 2012. 256 pages.
- Edward Gollin and Alexander Rehding, editors. The Oxford Handbook of Neo-Riemannian Music Theories. Oxford Handbooks. Oxford University Press, May 2014. 632 pages.
The general pattern in all of this work is a partial import, into music theory, of category theoretical diagrams coming from representation theory. Musical intervals, harmonic movement, and larger tonal structures all emerge from the action of the multiplicative monoid of integers
Neo-Riemannian theory generalizies tonnetze so that they model transformations between not just pitches, but between chords and more general musical datastructures. This use of tonnetze abstracts away from common practice tonal function and voice-leading, and some reject this development as overly formal and historically detached. But much of this suspicion stems from a misunderstanding of the depth of insight these tools offer. Far from being mere abstractions, tonnetze reveal profound geometries underlying harmonic motion β geometries that remain relevant even beyond traditional tonal music.
Embracing this perspective, we explore the tonnetz not as a historical artifact or static diagram, but as a dynamic analytic and generative tool for navigating the musical content of signals themselves.
We can understand the edges in our modernized version of Euler's tonnetz as multiplication operations. Indeed, moving up a perfect fifth corresponds to rescaling playback speed of a continuous audio signal
If we impose octave equivalence, then we ignore all factors of 2 when we rescale playback speed. Up to octave equivalence, movement up a perfect fifth amounts to rescaling the playback speed by any factor of 3, and movement up a major third amounts to rescaling the playback speed by a factor of 5, i.e.,
In this way, tonnetz-centric musical analysis can be derived from a theory of rescaling the playback speed of continous, periodic audio signals by integer factors:
Moving a continuous periodic audio signal f(t) up two octaves via f(t) β¦ f(2t) β¦ f(4t)
Not all audio signals are continuous though. Discrete audio signals have played and continue to play an important role in music production and in signal processing.
- π Audio Example: π A discrete periodic audio signal
For a discrete periodic audio signal, that is, for a periodic audio signal
Moving a discrete periodic audio signal s(i) "up two octaves" via s(i) β¦ s(2i) β¦ s(4i)
These modular, multiplicative transformations
If we take this proposal seriously, we arrive at the following:
- Question: How is movement along all these "discrete musical intervals" interelated?
If you've played with 8-bit tones, you may already have a sense that for discrete periodic audio signals, movement along musical intervals doesn't work in exactly the same way as it does for continous periodic audio signals. Discrete audio signals have complex timbres that seem to have mysterious relationships to one another.
A lot of this mystery can be calrified by modeling the signals as vertices in a directed graph that we call a discrete tonnetz, which depicts the multiplicative action of integers, modulo our sample count, as a categorical diagram. The set of nodes in this discrete tonnetz graph is the set of integers modulo our sample count, and each fixed integer
The discrete tonnetz captures how discrete spectral energy shifts under modular scaling, and reveals surprising orbit structures tied to the arithmetic of the modulus.
>>> from dissig.tonnetze.networks import Tonnetz # dissig Tonnetz classWe instantiate the discrete tonnetz for discrete signals with sample count
>>> modulus = 36 # Sample count for our discrete audio signals
>>> integer_list = [2, 3, 5, 7] # Integers to induce edges in tonnetz
>>> tonnetz = Tonnetz(modulus, integer_list) # Instantiate Tonnetz instanceWe can recover the graph underlying the tonnetz as a networkx.DiGraph instance via the Tonnetz.network attribute:
>>> print(tonnetz.network)
DiGraph with 36 nodes and 127 edgesThat said, if you pass this graph naively into a graph visualization library like networkx.drawing, the result is nearly unreadable β cluttered, asymmetric, and blind to the underlying modular symmetries that actually organize the space. For a quick example, we can import dissig's discrete tonnetz class Tonnetz:
Tonnetz for discrete audio signals with 36 samples
To better display discrete tonnetze, we need to use results from early number theory about the structure of the ring
The subset
These orbits form natural clusters in any discrete tonnetz. In fact, if we add all arrows coming from
In dissig, we provide a cluster-based visualization of any discrete tonnetz via the mode='dot' keyword argument of the nx_viz function. To provide an example, let us use our same discrete tonnetz for sample count
>>> from dissig.tonnetze.networks import Tonnetz
...
>>> modulus = 36
>>> integer_list = [2, 3, 5]
>>> tonnetz = Tonnetz(modulus, integer_list)But now, let us visulaize the tonnetz using nx_viz in 'dot' mode:
>>> from dissig.tonnetze.visualizers import nx_viz
...
>>> nx_viz(tonnetz, "test_viz", mode='dot')We get the following, better organized visualization of the same discrete tonnetz:
Complementary to the group of units inside
Here are several examples. To start, here's the 1-dimensional grid formed by the divisors of
Grid of divisors inside the discrete tonnetz for sample count
Next, here's the 2-dimensional grid formed by the divisors of
Grid of divisors inside the discrete tonnetz for sample count
And finally, here's the 3-dimensional grid formed by the divisors of
Grid of divisors inside the discrete tonnetz for sample count
In dissig, we also provide a visualization of any discrete tonnetz via the mode='neato' keyword argument of the nx_viz function:
>>> from dissig.tonnetze.networks import Tonnetz
>>> from dissig.tonnetze.visualizers import nx_vizTo provide an example, let us return to our discrete tonnetz for sample count
>>> modulus = 36
>>> integer_list = [2, 3, 5, 7]
>>> tonnetz = Tonnetz(modulus, integer_list)We get rather well organized version of this tonnetz if we pass to the nx_viz function with keyword argument 'neato':
>>> nx_viz(tonnetz, "test_viz", mode='neato')The resulting visualization does a fantastic job as clarifying the complimentary natrue of the grid of divisors (blue) and the unit group orbits (red):
A even better organized version of the tonnetz for sample count 36
Notice how subtracting
>>> modulus = 35 # Change modulus to 35
>>> tonnetz = Tonnetz(modulus, integer_list) # Use previous integer_list
>>> nx_viz(tonnetz, "test_viz", mode='neato', appearance_theme='dark')
A even better organized version of the tonnetz for sample count 35
One of the major advantages of thse better organized visualizations is that they let us draw quick conlusions about tonnetze for relatively large sample counts:
>>> modulus = 216 # Change modulus to 216 == 2**3 * 3**3
>>> integer_list = [2, 3, 5, 11, 13] # New integer_list
>>> tonnetz = Tonnetz(modulus, integer_list)
>>> nx_viz(tonnetz, "test_viz", mode='neato', appearance_theme='dark')
A even better organized version of the tonnetz for sample count 216
[...]
- π Installation
- π§ Usage
- π§ͺ Running Tests
- π Documentation
- π¦ Project Structure
- π Development
- π License
Using PDM:
pdm installOr using pip (if necessary):
pip install -e .Example usage:
>>> from dissig.tonnetze.networks import Tonnetz
>>> from dissig.tonnetze.visualizers import nx_viz
...
>>> modulus = 2**3 * 3**3
>>> integer_list = [2, 3, 5, 11, 13]
...
>>> tonnetz = Tonnetz(modulus, integer_list)
>>> nx_viz(tonnetz, "file_name")[...]
>>> from dissig.signals.discrete import character_signal
>>> from dissig.tonnetze.networks import SignalTonnetz
>>> from dissig.io.print_wav import tonnetz_to_wav
...
>>> modulus = 2**2 * 3**2
>>> integer_list = [2, 3, 5]
...
>>> signal = character_signal(1, modulus)
>>> signal_tonnetz = SignalTonnetz(signal, integer_list)
>>> tonnetz_to_wav(signal_tonnetz, 440.0, 1.0)pdm run pytest[...]
- Images: docs/images/
- LaTeX files: docs/tex/
- External references: docs/external/
.
βββ docs # Project documentation and resources
β βββ external
β βββ images
β βββ tex
βββ results # Output files generated by the code
β βββ tonnetze_visuals
β βββ wav_files
βββ src
β βββ dissig # Core project package
β βββ __init__.py
β βββ core.py # High-level pipeline functions and orchestration
β βββ io # Input/output utilities
β β βββ __init__.py
β β βββ print_wav.py # Functions for saving audio to WAV
β β βββ signal_to_wav()
β β βββ tonnetz_to_wav()
β βββ signals # Signal representations and processing
β β βββ __init__.py
β β βββ discrete.py # Discrete-time signal processing tools
β β βββ Signal # Main Signal class
β β β βββ scale_time_by()
β β β βββ extract_real()
β β β βββ __len__()
β β β βββ forward()
β β βββ character_signal() # Creates a character signal
β β βββ signal_from_real() # Wraps real data as Signal
β βββ tonnetze # Tonnetz network structures and visualizers
β β βββ __init__.py
β β βββ networks.py # Builds Tonnetz graph structures
β β β βββ Tonnetz # Main Tonnetz class
β β β β βββ generate_weighted_edges()
β β β β βββ generate_network()
β β β βββ SignalTonnetz # Tonnetz decorated with signals
β β β βββ generate_weighted_edges()
β β β βββ generate_network()
β β β βββ propogate_signal()
β β βββ visualizers.py # Graph rendering functions
β β βββ nx_viz_cluster() # Clustered node visualization
β β βββ nx_viz_neat() # Clean layout visualization
β βββ utils # Math utility functions
β βββ __init__.py
β βββ arithmetic.py # Number theoretic functions
β β βββ all_divisors()
β β βββ multiplicative_units()
β β βββ unit_clusters()
β βββ primes.py # Prime-related functions
β βββ primes_below()
β βββ prime_divisors()
β βββ prime_powers()
βββ tests # Unit tests for all modules
β βββ io
β βββ signals
β βββ tonnetze
β βββ utils
βββ LICENSE # License file (MIT License)
βββ README.md # Top-level project overview and instructions
βββ pdm.lock # PDM lock file for reproducible installs
βββ pyproject.toml # Project configuration (dependencies, metadata)
βββ pytest.ini # Pytest configuration file
βββ requirements.txt # Optional: basic dependency list for pip usersSet up your development environment:
pdm install --devTo enable import resolution in VSCode:
This project is licensed under the terms of the MIT License.