Extreme-fidelity synthesis of musical elements.
Music generates and manipulates sound in LPCM audio, sample by sample. It implements MASS (Music and Audio in Sample Sequences), a collection of psychophysical descriptions of musical elements expressed as equations and corresponding Python routines.
import music
# a chromatic scale, written to a WAV file
notes = [music.note(440 * 2 ** (i / 12), duration=0.25) for i in range(13)]
scale = [music.adsr(sonic_vector=n) for n in notes] # so the joins are silent
music.write_wav_mono(music.horizontal_stack(*scale), "scale.wav")The adsr is not decoration. A note ends wherever its phase lands and the
next one opens at the bottom of its wavetable, so concatenating them raw
steps -- by up to the full scale, and on nine of this scale's twelve joins.
An envelope runs each note to silence at both ends, which is what makes the
joins joins rather than clicks.
📖 Tutorial — from a single note to a short stereo piece. 📖 API reference — every routine documented with the equation it implements and the article it comes from.
- Sample-based synthesis. State is updated at every sample. A note with a vibrato has a different instantaneous frequency at each of its samples, and the vibrato pattern is folded into the wavetable lookup rather than applied afterwards, so the rendered sound is as close as it can be to the mathematical model that describes it.
- Musical structures with an emphasis on symmetry and discourse: permutation groups, change-ringing peals and plain changes.
- Music theory as the MASS companion paper states it: the seven diatonic modes and the single step pattern each is a rotation of, the three minor scales, the triads and the tetrads that extend them, the intervals and their traditional names, and the harmonic series. All in semitones from a tonic of zero, so a scale becomes frequencies and then sound in two steps.
- Filter design.
iirapplies coefficients;low_pass,high_pass,band_passandband_rejectcompute them, from the four designs the article specifies. - Head-related transfer functions.
setup_hrtf()fetches the KEMAR measurements into your cache andhrir()reads a direction out of them, so a source can be placed above you or behind you -- cues the geometriclocalizecannot carry. The package ships no measurements and models no head; it reads someone else's. - Bonds between a note's characteristics, so a piece decides once how its notes behave rather than note by note -- a vibrato that speeds up as the line rises, a tremolo that only appears above middle C.
- Sensory stimulation. Seven auditory stimuli -- binaural, monaural and
isochronic beats, amplitude and frequency modulation, modulated noise and
spatial motion -- each named for the technique it implements in
SSTIM, the Sensory Stimulation Vocabulary, and a
StimulationSessionthat renders a protocol of them: phases in order, crossfaded rather than cut, lasting exactly the sum of the durations you wrote down. The sample-accurate synthesis is the point here, because the frequency difference is the stimulus. play_audioto listen to a result without saving a file.
Music can be used alone or with other packages, and it is well suited to the audiovisualization of data. It works with Percolation and Participation for harnessing open linked social data, and with the audiovisual analytics vocabulary and ontology (AAVO).
To understand the routines further, read Musical elements in the discrete-time representation of sound. If you use this package, please cite that article.
Every release is archived on Zenodo, so a specific version can be cited too: 10.5281/zenodo.22151793 always resolves to the newest one. GitHub's Cite this repository button reads CITATION.cff and gives you both, formatted.
pip install musicRequires Python 3.10 or newer. Everything needed to synthesise, filter and write audio comes with it; the dependencies are declared in pyproject.toml.
One thing is optional. PrimaryTables.draw_tables(), which plots the waveform
tables so you can look at them, needs matplotlib:
pip install 'music[plot]'Nothing else in the package uses it, and leaving it out makes import music
about 40% faster.
To hack on it, install from a checkout so your edits take effect immediately:
git clone https://github.com/ttm/music.git
pip install -e musicEvery routine returns a numpy array of PCM samples, so results compose with each other and with anything else you can express in numpy.
note = music.note_with_vibrato(freq=220, duration=2,
vibrato_freq=6, max_pitch_dev=0.5)
shaped = music.adsr(sonic_vector=note, attack_duration=80,
sustain_level=-6, release_duration=200)Durations are in seconds, envelope stages in milliseconds, levels in decibels and pitch deviations in semitones — each parameter in the unit it is usually thought about in.
Everything is counted in semitones from a tonic of zero, which is what
pitch_to_freq takes:
freqs = music.pitch_to_freq(220.0, music.scale("dorian"))
chord = music.pitch_to_freq(220.0, music.chord("minor seventh"))
music.write_wav_mono(music.mix_many(
[music.note(f, duration=1.5) for f in chord]), "chord.wav")The seven modes are one step pattern read from seven places, and
mode_by_rotation reaches them that way rather than by name. Intervals
can be written the way a musician writes them:
music.interval("M3"), music.interval("P11") # (4, 17)
music.interval_names(6) # ('aug4', 'dim5', 'TT')
music.consonance("P4") # 'context dependent'Cutoffs are fractions of the sample rate — fraction_of converts Hertz —
so the same coefficients describe the same filter at any rate:
a, b = music.low_pass(music.fraction_of(1000))
muffled = music.iir(music.noise("white", duration=2), a, b)A vibrato whose rate follows the pitch, and a depth that falls as it rises:
voice = music.Bonds(vibrato_freq=music.proportional(1 / 40),
max_pitch_dev=music.inversely_proportional(400))
music.write_wav_mono(voice.render([220, 277, 330, 440], duration=0.5),
"bound.wav")localize puts a source on the ear axis from its geometry alone, so it
cannot tell front from back. A measured response can:
music.setup_hrtf() # once; about 1.3 MB into your cache
_, _, left, right = music.hrir(elevation=30, azimuth=270) # above, behind
music.write_wav_stereo(music.localize_hrtf(music.note(duration=2),
left, right), "behind.wav")Permutation groups and the peals of campanology, acted on any domain you like — here on frequencies, so the peal is the melody:
peal = music.PlainChanges(4) # every permutation, once
rows = peal.act([220, 275, 330, 440])
notes = [music.note(freq, duration=0.2) for row in rows for freq in row]
music.write_wav_mono(music.horizontal_stack(*notes), "campanology.wav")A source moving from one side to the other, its interaural time and intensity differences computed at every sample from its position:
passing = music.localize_linear(music.note(330, duration=3),
theta1=150, theta2=30, dist=0.6)
music.write_wav_stereo(passing, "passing.wav")seq = music.Sequencer()
for i, freq in enumerate([440, 550, 660]):
seq.add_note(freq, start=i * 0.25, duration=1.0,
adsr_params={"attack_duration": 20, "release_duration": 400})
seq.write("chord.wav")Six colours, each defined by its gain per octave — brown at −6 dB, pink at −3, white at 0, blue at +3, violet at +6, black at −12 — or any number you pass instead:
colours = [music.noise(kind, duration=0.5)
for kind in ("brown", "pink", "white", "blue", "violet")]
music.write_wav_mono(music.horizontal_stack(*colours), "colours.wav")Inside the examples folder you can find some scripts that use the main features of Music.
- chromatic_scale: writes twelve notes into a WAV file from a sequence of frequencies.
- penta_effects: writes a pentatonic scale repeated once clean, once with pitch, one with vibrato, one with Doppler, and one with FM, into a WAV stereo file.
- noisy: writes into a WAV file a sequence of different noises.
- thirty_notes and thirty_numpy_notes generate a sequence of sounds by using a synth class (in this case the class
Being). - campanology and geometric_music both use
Beingas their synth, but this time with permutations. - isynth also uses a synth class, but of a different kind,
IteratorSynth, that iterates through arbitrary lists of variables. - singing_demo: demonstrates
music.singing.setup_engine()andmusic.singing.make_test_song()to render a short sung phrase. - scales_and_chords: renders the seven modes, a I–vi–IV–V7 cadence, and sixteen partials of the harmonic series over their own fundamental, where the octaves land on the tempered scale and nothing else does.
- filtered_noise: puts each of the four filter designs on white noise — the useful thing to hear a filter on — and sweeps a low pass across five octaves.
- bonds: plays one line three times, changing only how its characteristics are bound to its pitch.
- binaural_beats: generates binaural beats using two pure tones with tremolo for relaxation or focus.
- sensory_stimulation: writes one file per SSTIM technique with
music.stimulation, and one three-phase session, which is the form these stimuli are actually delivered in. - The
music.singingmodule provides basic text-to-speech utilities. Runmusic.singing.setup_engine()once to clone the eCantorix engine before using these features. It is cloned into your user cache directory; setMUSIC_ECANTORIX_DIRto put it elsewhere. Because eCantorix is a Perl program driving espeak through a Makefile, it also needsgit,make,perlandespeakinstalled on the system —setup_engine()will tell you which are missing.
The modules are:
- core:
- synths for synthesization of notes (including vibratos, glissandos, etc.), noises and envelopes.
- filters for the application of filters such as ADSR envelopes, fades, IIR and FIR, reverb, loudness, and localization, and for designing the coefficients the IIR ones take.
- io for reading, writing and playing audio, both mono and stereo.
- functions for normalization.
- structures for higher level musical structures: permutations and the algebraic groups they form, change-ringing peals, and symmetry.
- theory for scales, chords and the harmonic series. Counterpoint, harmonic expansion and modulation are described in the companion paper and are not there yet.
- bonds for tying a note's vibrato and tremolo to its frequency, which is where an arbitrary construction of that kind goes.
- legacy for musical pieces that are rendered with the Music package and might be used as material to make more music.
- stimulation for sensory-stimulation work: the seven stimuli above, each carrying the SSTIM term it implements, and
StimulationSessionfor sequencing them into a protocol. - tables for the generation of lookup tables for some basic waveform.
- utils for various functions regarding conversions, mix, etc.
- sequencer for scheduling notes into a timeline and exporting audio.
Concrete things the code itself is waiting for, rather than a wish list:
- A head-related transfer function this package computes.
music.hrtfreads Gardner and Martin's KEMAR measurements, which closes the gap for anyone willing to fetch them, but the package models no head of its own: a direction nobody measured is answered with the nearest one somebody did. (localizeandlocalize2still carry neither elevation nor front-against-back, and say so in their own notes.) Being.walk'sperm-walkmethod, which was never restored from its predecessor and is currently a reconstruction. (Peals.twenty_all_overandPeals.an_eight_and_fortyno longer raise; this entry used to say they did.)- Counterpoint, harmonic expansion and modulation, which the companion
paper describes and
music.theorydoes not implement. - Checking the routines MASS has no counterpart for.
RECONCILIATION.mdcompares the package with the MASS reference implementation routine by routine — 26 of 35 are reproduced sample for sample, and the rest diverge for reasons it states.tests/test_article.pychecks the article's equations instead of its code, and covers all 45 of the 47 that a test could settle. Both leavemusic.stimulation,music.singing,music.structuresand the sequencer with nothing to be measured against.DISCREPANCIES.mdis where the article, the reference implementation and this package are recorded as disagreeing. - An article describing the package, as a companion to the MASS one.
CONTRIBUTING.md is the longer version: what a change has
to survive, why a test that checks the shape of the output is worth little
here, and how to report something that sounds wrong. Participation is under
the Code of Conduct.
The test, type-check, lint and documentation tooling comes with the dev and
docs extras:
pip install -e '.[dev,docs]'pytest # 3,412 tests, 100% line and branch coverage
mypy music # type check
ruff check music tests examples tools conftest.py # lint, at PEP 8's 79 columns
sphinx-build -b html -W docs docs/_build/html
python tools/run_examples.py # run every example
python tools/assessment_figures.py # the docs' figures vs the packageAll six run on every push and every pull request. Lint, types and tests run on Python 3.10 through 3.14; a further job
installs the exact lower bounds pyproject.toml declares and runs the
tests against those, so the floors cannot drift into fiction. The docs
build, the examples and the figures check run once each. pytest and
sphinx-build are configured to fail on anything less than full coverage
or a docstring numpydoc cannot parse.
run_examples.py is there because the other four look at the package and
none of them looks at a caller. The examples are the only callers this
repository has, and a change that broke three of them once passed every
other check. assessment_figures.py is there because the numbers in
ASSESSMENT.md and this file went stale four times in two days when
keeping them current was a habit rather than a check.
Four more run at release time rather than on every push, being slower or needing something the runner does not have:
python tools/check_sdist.py # build the sdist, unpack it, run its tests
python tools/article_coverage.py # which of the article's equations are checked
python tools/mass_reconcile.py # this package against the MASS reference
python tools/verify_subjects.py # the archival subjects resolve to their termsThe last three need a MASS checkout or the
network; RECONCILIATION.md and DISCREPANCIES.md are what they produce.
Docstrings are numpydoc style throughout, and the code follows PEP 8. For the maths behind a routine, examples of its use, and the article it comes from, read its docstring — or the rendered API reference.
music has been developed and maintained in the open since 2016. If it is
useful to you, your research or your institution, please consider supporting
its continued development through
GitHub Sponsors.
Sponsorship pays for the unglamorous work that makes a scientific package trustworthy — the fidelity tests, the full coverage, the documented equations, the archived and citable releases — and keeps every bit of it free for everyone.
For institutions and companies: commissioned features, integration support and sponsored development are available, with the results released under the same open license. Open an issue or get in touch to discuss scope.
Music is primarily intended for artistic use, psychophysics experiments and data sonification.
You can find an example in Versinus, an animated visualization method for evolving networks that uses Music to render the musical track that represents networks structures.