jchord - toolkit for working with chord progressions¶
What’s this then?¶
jchord
is a Python package which provides tools for working with chord progressions. jchord
:
has object representations for notes, chords, and progressions (in the Western 12-tone system)
knows about naming conventions for chords, and can convert back and forth between objects and names
can be used as a converter between strings, text files, XLSX files, PDFs and MIDI files (see “converter script” below)
Get it¶
Basic installation:
pip install jchord
Installation with dependencies for reading and writing MIDI/XLSX/PDF files:
pip install jchord[midi,xlsx,pdf]
Convert between formats¶
If you just want the converter functionality, invoke jchord on the command line:
usage: jchord [-h] [--midi MIDI] [--pdf PDF] file_in file_out
Converts between different representations of the same format
positional arguments:
file_in Input progression as string, .txt, .xlsx or .midi
file_out Output file as .txt, .xlsx, .midi or .pdf
optional arguments:
-h, --help show this help message and exit
--midi MIDI comma separated list of arguments for midi, e.g. tempo=8,beats_per_chord=2
--pdf PDF comma separated list of arguments for pdf, e.g. chords_per_row=8,fontsize=30
Example:
jchord "Cm A E7 F#m7" example.mid --midi tempo=80,beats_per_chord=1
As a library¶
Here is an example that parses a chord progression written as a string, transposes it upwards by 2 semitones, converts it back to a string and then creates a midi file from it.:
from jchord import ChordProgression, MidiConversionSettings
prog = ChordProgression.from_string("C -- Fm7 -- C -- G7 -- C -- E7 Am F Bm7b5 E7 Am9 F Bo C69 --")
prog = prog.transpose(+2)
print(prog.to_string())
prog.to_midi(MidiConversionSettings(filename="example.midi", tempo=100, beats_per_chord=2, instrument=4))
Output:
D -- Gm7 --
D -- A7 --
D -- F#7 Bm
G C#m7b5 F#7 Bm9
G C#o D69 --
For more examples, see the documentation.
Documentation¶
Documentation lives here: jonathangjertsen.github.io/jchord/
Contributing¶
To contribute, open an issue or create a Pull Request in the Github repo.
Examples¶
The following examples make use of the MIDI subsystem in jchord
, which uses the mido
package.
Installing with pip install jchord[midi]
will ensure this package is installed.
Example: Autumn Leaves in two styles¶
This example generates two MIDI files: one with fancy synth arpeggios, another with a basic strumming guitar.
from jchord.progressions import ChordProgression, MidiConversionSettings
from jchord.midi import Instrument
from jchord.midi_effects import (
Chain,
Doubler,
Arpeggiator,
Spreader,
Transposer,
)
progression = ChordProgression.from_string(
"""
Dm -- G -- C Cadd9 Fmaj7 --
Dm -- E -- Am -- Am9 --
Dm7 -- G7 -- C C7 Fmaj7 Fmaj7add6
Dm9 -- E7 -- Am -- Amadd6 --
Esus4 E7 Am --
Dm7 -- G -- Cmaj9 Cmaj9/G Fmaj7 --
Dm -- E -- Amadd9 Am/G Am/F# Fmaj7
E7b9 -- Amadd6add9 -- -- -- Am --
"""
)
progression.to_midi(
MidiConversionSettings(
filename="autumn_leaves_arpeggiated.midi",
tempo=110,
beats_per_chord=2,
instrument=Instrument.VoiceOohs,
effect=Chain(
Doubler(12),
Arpeggiator(
rate=1 / 16,
pattern=[
(0, 2), 1, 2, (1, 3),
2, (3, 4), 2, 3,
(4, 1), 3, 4, 5,
(-2, 0), -3, -4, -5,
],
sticky=True,
)
),
)
)
progression.to_midi(
MidiConversionSettings(
filename="autumn_leaves_strummed.midi",
tempo=180,
beats_per_chord=2,
instrument=Instrument.AcousticGuitarSteel,
effect=Chain(Transposer(-12), Spreader(amount=30, jitter=6)),
velocity=50,
)
)
Example: Parse MIDI¶
jchord
can parse MIDI files that are a sequence of block chords.
It uses a kernel density estimation algorithm to group notes into chords, allowing it to handle slight imperfections.
If the file has arpeggios, melodies or other flourishes, it will not work.
from pathlib import Path
from jchord import ChordProgression
prog = ChordProgression.from_midi_file(Path(__file__).parent.parent / "test" / "test_data" / "issue_8.mid")
print(prog.to_string())
Example: Harmonization¶
This example shows the Harmonizer
effect, building 7th chords from single notes. Use the n suffix to indicate a single note.
from jchord.midi import Instrument
from jchord.midi_effects import Harmonizer
progression = ChordProgression.from_string("""Fn Gn En An""")
progression.to_midi(
MidiConversionSettings(
filename="harmonizer.midi",
tempo=85,
beats_per_chord=2,
instrument=Instrument.VoiceOohs,
effect=Harmonizer(scale=[0, 2, 4, 5, 7, 9, 11], degrees=[1, 3, 5, 7], root="C"),
)
)
API reference¶
Objects at a glance¶
These are the key objects defined by jchord:
Note
Intervals
Chord
ChordProgression
Song
The highest-level object is Song
.
A Song
has a sections
property, which is a list of ChordProgression
objects.
A ChordProgression
has a progression
property, which is a list of Chord
objects.
A Chord
object has a chord
property, which is a Intervals
object; and a root
property, which is a Note
object.
Working with individual notes: Note
¶
A Note
represents one of the musical notes in the Western 12-tone system.
It is identified by its pitch class and octave according to Scientific pitch notation; so Note('C', 4)
is Middle C.
- class jchord.Note(name: str, octave: int)¶
Represents an absolute note with a name and an octave. To create a Note, provide the name as a string and the octave as an integer:
>>> Note(name="A", octave=3) Note('A', 3)
Two instances of
Note
are equal if they have the same name and octave, or if they have the same octave and their names are enharmonic:>>> Note('G#', 4) == Note('Ab', 4) True >>> Note('G#', 4) == Note('Ab', 3) False
You can subtract two
Note
instances to get the number of semitones between them:>>> Note('G#', 4) - Note('Ab', 3) 12 >>> Note('Ab', 3) - Note('G#', 4) -12
Note
is hashable, so instances can be used as dictionary keys or members of sets.- pitch() float ¶
Returns the absolute pitch of the note in Hz.
>>> Note("A", 4).pitch() 440.0 >>> Note("A", 0).pitch() 27.5 >>> Note("C", 4).pitch() 261.6255653005986
Working with individual chords: Intervals
and Chord
¶
The meaning of a chord is slightly ambiguous, and jchord has two classes to disambiguate between the case where we refer to a chord with a root (say, “Am7” or “C#maj7#11”), and the case where we refer to the interval structure without reference to any particular root note (say, “maj7” or “min11”):
Chord
refers to a root note AND the interval structure.Intervals
refers to just the interval structure.
- class jchord.Intervals(semitones: List[int], name: str, implicit_zero=True, source_chord=None)¶
Represents an interval structure or chord quality, which can be thought of as a chord without reference to any particular root note. Examples of interval structures are major, minor, dominant, major 7, etc.
There are several ways to construct
Intervals
. You can either specify the name or the interval structure of the chord, and the other will be inferred.To specify the name and infer the semitones, use
from_name
:>>> Intervals.from_name("maj7") Intervals(name='maj7', semitones=[0, 4, 7, 11]) >>> Intervals.from_name("7sus4#13") Intervals(name='7sus4#13', semitones=[0, 5, 7, 10, 14, 17, 22])
If nothing can be generated from the name, an
InvalidChord
exception is raised.To specify the semitones and infer the name, use
from_semitones
orfrom_degrees
.>>> Intervals.from_semitones([0, 3, 7, 11]) Intervals(name='minmaj7', semitones=[0, 3, 7, 11]) >>> Intervals.from_semitones([3, 7, 11]) # the 0 is optional Intervals(name='minmaj7', semitones=[0, 3, 7, 11]) >>> Intervals.from_semitones([3, 7, 11], name='mMaj7') # you can override the name Intervals(name='mMaj7', semitones=[0, 3, 7, 11]) >>> Intervals.from_semitones([1, 2, 3, 4]) # no common name Intervals(name='<unknown>', semitones=[0, 1, 2, 3, 4])
- add_semitone(semitone: int, recalculate_name: bool = True) Intervals ¶
Returns a new
Chord
where the given semitone (as a difference from the root degree) has been added. The name will be recalculated unless the optional parameterrecalculate_name
is set toFalse
.>>> Intervals.from_name("m").add_semitone(10) Intervals(name='min7', semitones=[0, 3, 7, 10])
- interval_sequence() List[int] ¶
Returns the list of internal intervals in the chord.
>>> Intervals.from_name("minor").interval_sequence() [3, 4] >>> Intervals.from_name("major7").interval_sequence() [4, 3, 4]
- remove_semitone(semitone: int, recalculate_name: bool = True) Intervals ¶
Returns a new
Chord
where the given semitone (as a difference from the root degree) has been removed (if it was present). The name will be recalculated unless the optional parameterrecalculate_name
is set toFalse
.>>> Intervals.from_name("maj7").remove_semitone(11) Intervals(name='', semitones=[0, 4, 7])
- rotate_semitones(n: int, recalculate_name: bool = True) Intervals ¶
Returns a new
Intervals
where the semitones have been rotated,n
times. In other words,chord.rotate_semitones(1)
is the 1st inversion ofchord
,chord.rotate_semitones(2)
is the 2nd inversion, etc. The name will be recalculated unless the optional parameterrecalculate_name
is set to False.>>> Intervals.from_name("maj7").rotate_semitones(2) Intervals(name='maj7inv2', semitones=[7, 11, 12, 16], implicit_zero=False)
- class jchord.Chord(name: str, root: Note, intervals: Intervals)¶
Represents a concrete chord with a root note and an interval structure.
There are several ways to construct a
Chord
. You can either specify the name or a root note + interval structure of the chord, and the other will be inferred.To specify the name and infer the notes, use
from_name
:>>> Chord.from_name("Amaj7") Chord(name='Amaj7', root=Note('A', 4), intervals=Intervals(name='maj7', semitones=[0, 4, 7, 11])) >>> Chord.from_name("B7sus4#13") Chord(name='B7sus4#13', root=Note('B', 4), intervals=Intervals(name='7sus4#13', semitones=[0, 5, 7, 10, 14, 17, 22]))
Chord
supports all the same names asIntervals
, as well as slash chords.>>> Chord.from_name("Amaj7/C") Chord(name='Amaj7/C', root=Note('A', 4), intervals=Intervals(name='<unknown>', semitones=[-9, 0, 4, 7, 11]))
If no chord can be generated from the name, an
InvalidChòrd
exception is raised.To specify the root and semitones and infer the name, use
from_root_and_semitones
.>>> Chord.from_root_and_semitones(Note('A', 5), [0, 3, 7, 11]) Chord(name='Aminmaj7', root=Note('A', 5), intervals=Intervals(name='minmaj7', semitones=[0, 3, 7, 11]))
In addition to the methods above, you can specify a set of MIDI values:
>>> Chord.from_midi({ 77, 80, 84 }) Chord(name='Fmin', root=Note('F', 5), intervals=Intervals(name='min', semitones=[0, 3, 7]))
- property bass: Note¶
Returns the lowest note in the chord.
Unless the chord is a slash chord, this is the same as
chord.root
.>>> Chord.from_name("Am7").bass Note('A', 4) >>> Chord.from_name("Am7/B").bass Note('B', 3)
- interval_sequence() List[int] ¶
Returns the semitones in the chord.
Returns the list of internal intervals in the chord.
>>> Chord.from_name("Aminor").interval_sequence() [3, 4] >>> Chord.from_name("Amajor7").interval_sequence() [4, 3, 4]
- midi() List[int] ¶
Returns the list of MIDI note values in the chord.
>>> Chord.from_name("Amajor7").midi() [69, 73, 76, 80]
- property semitones: List[int]¶
Returns the semitones in the chord.
>>> Chord.from_name("Am7").semitones [0, 3, 7, 10]
- class jchord.InvalidChord¶
Raised if trying to construct a chord from an invalid chord name.
Working with chord progressions: ChordProgression
¶
A chord progression is represented as a list of chords, one after another.
- class jchord.ChordProgression(progression: List[Chord])¶
Represents a chord progression.
There are many ways to create a
ChordProgression
object.From a string
Use the
from_string
method to generate a chord progression from a string.>>> ChordProgression.from_string("Dm7 -- Gm7 Am7") ChordProgression([Chord(name='Dm7', root=Note('D', 4), intervals=Intervals(name='m7', semitones=[0, 3, 7, 10])), Chord(name='Dm7', root=Note('D', 4), intervals=Intervals(name='m7', semitones=[0, 3, 7, 10])), Chord(name='Gm7', root=Note('G', 4), intervals=Intervals(name='m7', semitones=[0, 3, 7, 10])), Chord(name='Am7', root=Note('A', 4), intervals=Intervals(name='m7', semitones=[0, 3, 7, 10]))])
From a text file
Use the
from_txt
method to generate a chord progression from a text file. If example.txt contains the text “Am7 D7”, thenChordProgression.from_txt("example.txt")
will produce the same result asChordProgression.from_string("Am7 D7")
.From an Excel file
Use the
from_xlsx
method to generate a chord progression from an Excel spreadsheet. If example.xlsx contains the following cells:C
D
Em7
G7
Then the result is equivalent to calling
ChordProgression.from_string("C D Em7 G7")
.Note
This feature requires
openpyxl
, which you can get withpip install openpyxl
.From a MIDI file
Use the
from_midi
method to generate a chord progression from a MIDI file. Ifexample.mid
contains some chords that you would like to convert to aChordProgression
, useChordProgression.from_midi("example.mid")
. For best results, the MIDI file should contain a single instrument with chords played as straight as possible.Note
This feature requires
mido
, which you can get withpip install mido
.- chords() Set[Chord] ¶
Returns the set of chords in the progression.
>>> ChordProgression.from_string("Am7 D7").chords() {Chord(name='D7', root=Note('D', 4), intervals=Intervals(name='7', semitones=[0, 4, 7, 10])), Chord(name='Am7', root=Note('A', 4), intervals=Intervals(name='m7', semitones=[0, 3, 7, 10]))}
- midi() List[List[int]] ¶
Returns the MIDI values for each chord in the progression.
>>> ChordProgression.from_string("Am7 D7").midi() [[69, 72, 76, 79], [62, 66, 69, 72]]
- to_midi(settings: MidiConversionSettings, **kwargs)¶
Saves the chord progression to a MIDI file.
Note
This feature requires
mido
, which you can get withpip install mido
.
- to_string(chords_per_row: int = 4, column_spacing: int = 2, newline: str = '\n') str ¶
Returns the string representation of the chord progression.
- to_txt(filename: str, chords_per_row: int = 4, column_spacing: int = 2, newline: str = '\n')¶
Saves the string representation of the chord progression to a text file.
- to_xlsx(filename: str, chords_per_row: int = 4)¶
Saves the chord progression to an Excel file.
Note
This feature requires
openpyxl
, which you can get withpip install openpyxl
.
- transpose(shift: int)¶
Transposes all chords in the progression by the given shift.
>>> ChordProgression.from_string("Am7 D7").transpose(2).to_string().strip() 'Bm7 E7'
- class jchord.MidiConversionSettings(filename: str, instrument: int = 1, tempo: int = 120, beats_per_chord: Union[int, list] = 2, velocity: int = 100, repeat: str = 'replay', effect=None)¶
MIDI features¶
Tools for working with MIDI.
- class jchord.midi_effects.MidiEffect(*args, **kwargs)¶
Base class for MIDI effects
- apply(chord: List[MidiNote])¶
Returns a list where the effect has been applied to the given chord
- set_settings(settings: MidiConversionSettings)¶
Makes the MIDI conversion settings available. This is always applied before apply() during the MIDI conversion