Python toolkit for working with chord progressions
Project description
jchord - Python toolkit for working with chord progressions
>>> from jchord.progressions import ChordProgression
>>> prog = ChordProgression.from_string("C -- Fm7 -- C -- G7 -- C -- E7 Am F Bm7b5 E7 Am9 F Bo C69 --")
>>> print(prog.to_string())
C -- Fm7 --
C -- G7 --
C -- E7 Am
F Bm7b5 E7 Am9
F Bo C69 --
>>> prog.to_midi("example.midi", tempo=100, beats_per_chord=2, instrument=4)
- Chord progressions can be imported from:
- string (
prog = ChordProgression.from_string(string)
) - text file (
prog = ChordProgression.from_txt(filename)
) - XLSX file (
prog = ChordProgression.from_xlsx(filename)
)
- string (
- Chord progressions can be exported to:
- string (
print(prog.to_string(...optional))
) - text file (
prog.to_txt(filename, ..optional)
) - XLSX file (
prog.to_xlsx(filename, ...optional)
) - MIDI file (
prog.to_midi(filename, ...optional)
)
- string (
Install
pip install jchord
- If you want to use the MIDI and XLSX functionality, also
pip install mido openpyxl
Development
Clone the repo, and in the folder run pip install -r requirements_dev.txt
Testing
To run the tests:
pytest --cov-report term-missing --cov jchord -vvv
Make sure all tests pass and that the code has 100% test coverage.
Formatting
The code is formatted with black. In the root of the repo, run:
black .
Documenting
To generate documentation, run the documentation generation script:
python doc_gen/generate_doc.py -o README.md
The script appends documentation from each source module to doc_gen/index.md
. If the script ran successfully, README.md should contain a section called "Documentation" below.
To decide which parts should be present in the documentation, go and edit doc_gen/pydocmd.yml
. The reason for the format is that I originally wanted to use pydocmd
(but found it too limiting).
Documentation
Table of contents
jchord.core
- Class
Note
- Method
Note.__init__(self, name: str, octave: int)
- Method
Note.pitch(self) -> float
- Method
Note.transpose(self: 'Note', shift: int) -> 'Note'
- Method
Note.transpose_degree(self: 'Note', shift: str) -> 'Note'
- Method
- Class
CompositeObject
- Method
CompositeObject._keys(self) -> Hashable
- Method
- Function
split_to_base_and_shift(name_or_degree: str, name_before_accidental: bool) -> (<class 'str'>, <class 'int'>)
- Function
degree_to_semitone(degree: str) -> int
- Function
semitone_to_degree_options(semitone: int, max_accidentals: int = 1) -> List[str]
- Function
note_diff(name_low: str, name_high: str) -> int
- Exception
InvalidDegree
- Class
jchord.midi
- Class
PlayedNote
- Method
PlayedNote.time(self)
- Method
PlayedNote.note(self)
- Method
PlayedNote.duration(self)
- Method
- Function
note_to_midi(note: jchord.core.Note) -> int
- Function
midi_to_note(midi: int) -> jchord.core.Note
- Function
midi_to_pitch(midi: int) -> float
- Function
read_midi_file(filename: str) -> List[jchord.midi.PlayedNote]
- Function
group_notes_to_chords(notes: List[jchord.midi.PlayedNote]) -> Dict[float, jchord.midi.PlayedNote]
- Exception
InvalidNote
- Class
jchord.knowledge
jchord.chords
- Class
Chord
- Method
Chord.__init__(self, name: str, semitones: List[int])
- Method
Chord.from_semitones(name: Union[str, NoneType], semitones: List[int]) -> 'Chord'
- Method
Chord.from_degrees(name: str, degrees: List[str]) -> 'Chord'
- Method
Chord.from_name(name: str) -> 'Chord'
- Method
Chord.intervals(self) -> List[int]
- Method
Chord.with_root(self, root: jchord.core.Note) -> 'ChordWithRoot'
- Method
Chord.add_semitone(self, semitone: int)
- Method
- Class
ChordWithRoot
- Method
ChordWithRoot.__init__(self, name: str, root: jchord.core.Note, chord: jchord.chords.Chord)
- Method
ChordWithRoot.from_root_and_semitones(root: jchord.core.Note, semitones: List[int]) -> 'ChordWithRoot'
- Method
ChordWithRoot.from_midi(midi: Set[int]) -> 'ChordWithRoot'
- Method
ChordWithRoot.from_name(name: str, octave: int = 4) -> 'ChordWithRoot'
- Method
ChordWithRoot.semitones(self)
- Method
ChordWithRoot.intervals(self) -> List[int]
- Method
ChordWithRoot.midi(self) -> List[int]
- Method
ChordWithRoot.transpose(self, shift: int) -> 'ChordWithRoot'
- Method
- Function
semitones_to_chord_name_options(semitones: Set[int], _rec=5) -> List[str]
- Exception
InvalidChord
- Class
jchord.progressions
- Class
ChordProgression
- Method
ChordProgression.__init__(self, progression: List[jchord.chords.ChordWithRoot])
- Method
ChordProgression.from_string(string: str) -> 'ChordProgression'
- Method
ChordProgression.from_txt(filename: str) -> 'ChordProgression'
- Method
ChordProgression.from_xlsx(filename: str) -> 'ChordProgression'
- Method
ChordProgression.from_midi_file(filename: str) -> 'ChordProgression'
- Method
ChordProgression.chords(self) -> Set[jchord.chords.ChordWithRoot]
- Method
ChordProgression.midi(self) -> List[List[int]]
- Method
ChordProgression.to_string(self, chords_per_row: int = 4, column_spacing: int = 2, newline: str = '\n') -> str
- Method
ChordProgression.to_txt(self, filename: str, chords_per_row: int = 4, column_spacing: int = 2, newline: str = '\n')
- Method
ChordProgression.to_xlsx(self, filename: str, chords_per_row: int = 4)
- Method
ChordProgression.to_midi(self, filename: str, instrument: int = 1, tempo: int = 120, beats_per_chord: Union[int, list] = 2, velocity: int = 100)
- Method
- Class
SongSection
- Method
SongSection.name(self)
- Method
SongSection.progression(self)
- Method
- Class
Song
- Method
Song.__init__(self, sections: List[jchord.progressions.SongSection])
- Method
Song.to_string(self, chords_per_row: int = 4, column_spacing: int = 2, newline: str = '\n')
- Method
- Exception
InvalidProgression
- Class
jchord.core
Basic utilities for working with notes, base classes for objects etc.
Classes
Note
Represents an absolute note with a name and an octave.
Two Note
s are equal if they have the same name and octave,
or if they have the same octave and their names are enharmonic
(so Note('A#', 4) == Note('Gb', 4)
)
Methods
__init__(self, name: str, octave: int)
pitch(self) -> float
Returns the absolute pitch of the note in Hz.
Examples (equalities are approximate):
Note("A", 4).pitch() == 440.0
Note("A", 0).pitch() == 27.5
Note("C", 4).pitch() == 261.62556
transpose(self: 'Note', shift: int) -> 'Note'
Transposes the note by the given number of semitones.
Examples:
Note("C", 0).transpose(1) == Note("C#", 0)
Note("C", 4).transpose(19) == Note("G", 5)
transpose_degree(self: 'Note', shift: str) -> 'Note'
Transposes the given note by the given scale degree
Examples:
Note("C", 0).transpose_degree("b2") == Note("C#", 0)
Note("C", 4).transpose_degree("12") == Note("G", 5)
CompositeObject
Base class for objects which can be represented by a few attributes.
Subclasses must implement a _keys()
function which returns a hashable
representation of these attributes (e.g. returns them as a tuple).
A CompositeObject is considered equal to another if the return value of their
_keys()
function is equal.
Iterating over a CompositeObject is like iterating over the return value of its
_keys()
function.
Methods
_keys(self) -> Hashable
Returns a hashable representation of the attributes that the object wraps.
Functions
split_to_base_and_shift(name_or_degree: str, name_before_accidental: bool) -> (<class 'str'>, <class 'int'>)
Takes a string representation of a note name or a degree. Returns a tuple where the first element is the string representation of the degree with accidentals removed, and the second element is the number of semitones needed to account fo accidentals.
Examples:
split_to_base_and_shift("A#", name_before_accidental=True) == ("A", 1)
split_to_base_and_shift("Ab", name_before_accidental=True) == ("A", -1)
split_to_base_and_shift("A", name_before_accidental=True) == ("A", 0)
split_to_base_and_shift("Abbbb", name_before_accidental=True) == ("A", -4)
split_to_base_and_shift("b9", name_before_accidental=False) == ("9", -1)
split_to_base_and_shift("#9", name_before_accidental=False) == ("9", 1)
degree_to_semitone(degree: str) -> int
Converts a string representation of a scale degree to the number of semitones between the root and that scale degree.
Examples:
degree_to_semitone("b9") == 13
degree_to_semitone("5") == 7
semitone_to_degree_options(semitone: int, max_accidentals: int = 1) -> List[str]
Converts the number of semitones between the root and the wanted scale degree to a list of possible names for that scale degree.
The list of options is sorted by "reasonableness":
- If option A has fewer accidentals than option B, option A comes first.
- If option A has the same number of accidentals as option B, then the option with a "b" comes before the option with a "#".
Examples:
semitone_to_degree_options(semitone=3, max_accidentals=1) = ["b3", "#2"]
semitone_to_degree_options(semitone=3, max_accidentals=0) = []
semitone_to_degree_options(semitone=17, max_accidentals=1) = ["11", "#10"]
note_diff(name_low: str, name_high: str) -> int
Returns the number of semitones between the first note and the second note. The first note is assumed to be the lower of the two notes.
Examples:
note_diff("G", "A") == 2
note_diff("A", "Bb") == 1
note_diff("A", "G") == 10
note_diff("A", "A") == 0
Exceptions
InvalidDegree
Raised if the string one attempts to interpret is not a valid scale degree
jchord.midi
Tools for working with MIDI.
Classes
PlayedNote
namedtuple which represents a (MIDI) note played at a given time for a given duration.
Methods
time(self)
Alias for field number 0
note(self)
Alias for field number 1
duration(self)
Alias for field number 2
Functions
note_to_midi(note: jchord.core.Note) -> int
Returns the midi value corresponding to the given Note
.
midi_to_note(midi: int) -> jchord.core.Note
Returns the Note
corresponding to the given MIDI note value.
midi_to_pitch(midi: int) -> float
Returns the absolute pitch in Hz for the given MIDI note value.
read_midi_file(filename: str) -> List[jchord.midi.PlayedNote]
Reads the MIDI file for the given filename and returns the corresponding list of PlayedNote
s.
group_notes_to_chords(notes: List[jchord.midi.PlayedNote]) -> Dict[float, jchord.midi.PlayedNote]
Groups the list of PlayedNote
s by time.
The return value maps time to a list of PlayedNote
s for that time.
There is no attempt at quantization at this time, so the notes must be played at the exact same time to be grouped together.
Exceptions
InvalidNote
Raised when trying to get the MIDI value of a note that doesn't seem valid.
jchord.knowledge
Contains various literals that are used for computations throughout the library.
REPETITION_SYMBOL
(str
): The symbol that's used to indicate repetition in textual representations of chord progressions.MAJOR_SCALE_OFFSETS
(Dict[int, int]
): Maps the scale degrees of the major scale to number of semitones from the root.ACCIDENTALS
(Set[str]
): The set of accidentals.MAJOR_FROM_C
(List[str]
): A list of the 7 note names in the C major scale.CHROMATIC
(List[str]
): A list of the 12 chromatic notes starting from C. Only sharp notes are included here.ENHARMONIC
(List[Tuple[str, str]])
): A list of the 5 pairs of enharmonic note names.CHORD_NAMES
(Dict[str, List[str]]
): Maps chord names to the list of scale degrees in the chord, not including the root.CHORD_ALIASES
: (Dict[str, str]
): Maps alternative chord names to the canonical chord name inCHORD_NAMES
.DYADS
: (Dict[int, str]
): A collection of two-note chords with names. Maps the number of semitones between the root and the other note to the chord name.TRIADS_WITH_FIFTH
(Dict[int, str]
): A collection of three-note chords with names. All of these triads include a fifth. Maps the semitone that is not the root or the fifth to the chord name.
jchord.chords
Tools for working with chords.
Classes
Chord
Represents a chord quality (no root).
Methods
__init__(self, name: str, semitones: List[int])
from_semitones(name: Union[str, NoneType], semitones: List[int]) -> 'Chord'
Creates a Chord from a list of semitones from the root.
If name
is None
, semitones_to_chord_name_options
is used to guess a good name for the chord.
from_degrees(name: str, degrees: List[str]) -> 'Chord'
Creates a Chord from a list of scale degrees.
If name
is None
, semitones_to_chord_name_options
is used to guess a good name for the chord.
from_name(name: str) -> 'Chord'
Creates a Chord from a name.
The CHORD_NAMES
and CHORD_ALIASES
dictionaries in jchord.knowledge
are used to find the semitones in the chord.
Raises InvalidChord
if no chord is found.
intervals(self) -> List[int]
Returns the list of internal intervals in the chord.
Examples:
Chord.from_name("minor").intervals() == [3, 4]
Chord.from_name("major7").intervals() == [4, 3, 4]
with_root(self, root: jchord.core.Note) -> 'ChordWithRoot'
Returns a ChordWithRoot
based on the chord and the provided root.
add_semitone(self, semitone: int)
Adds the given semitone (as a difference from the root degree) to the chord.
The name of the chord does not get re-calculated, so use with care.
ChordWithRoot
Represents a chord with a chord quality and a root note.
Methods
__init__(self, name: str, root: jchord.core.Note, chord: jchord.chords.Chord)
from_root_and_semitones(root: jchord.core.Note, semitones: List[int]) -> 'ChordWithRoot'
Creates a ChordWithRoot from a root Note
and a list of semitones from the root.
semitones_to_chord_name_options
is used to guess a good name for the chord.
from_midi(midi: Set[int]) -> 'ChordWithRoot'
Creates a ChordWithRoot from a set of MIDI note values.
semitones_to_chord_name_options
is used to guess a good name for the chord.
from_name(name: str, octave: int = 4) -> 'ChordWithRoot'
Creates a ChordWithRoot from a name.
semitones_to_chord_name_options
is used to guess a good name for the chord.
semitones(self)
Returns the semitones in the chord.
intervals(self) -> List[int]
Returns the semitones in the chord.
midi(self) -> List[int]
Returns the list of MIDI note values in the chord.
transpose(self, shift: int) -> 'ChordWithRoot'
Transposes the chord by the given shift (in semitones).
Functions
semitones_to_chord_name_options(semitones: Set[int], _rec=5) -> List[str]
Returns a set of chord names corresponding to the given set of semitones.
The function tries to put the most reasonable chord names first and the more dubious ones last.
The _rec
argument is for internal use only; in some cases, it prevents infinite recursion while computing the chord name.
Exceptions
InvalidChord
Raised if trying to construct a chord from an invalid chord name.
jchord.progressions
Tools for working with chord progressions.
Classes
ChordProgression
Represents a chord progression.
Methods
__init__(self, progression: List[jchord.chords.ChordWithRoot])
from_string(string: str) -> 'ChordProgression'
Creates a ChordProgression
from its string representation.
from_txt(filename: str) -> 'ChordProgression'
Creates a ChordProgression
from a text file with its string representation.
from_xlsx(filename: str) -> 'ChordProgression'
Creates a ChordProgression
from an Excel file.
from_midi_file(filename: str) -> 'ChordProgression'
Creates a ChordProgression
from an MIDI file.
There is no attempt at quantization at this time, so the notes must be played at the exact same time to be grouped together as chords.
chords(self) -> Set[jchord.chords.ChordWithRoot]
Returns the set of chords in the progression.
midi(self) -> List[List[int]]
Returns the MIDI values for each chord in the progression.
to_string(self, chords_per_row: int = 4, column_spacing: int = 2, newline: str = '\n') -> str
Returns the string representation of the chord progression.
to_txt(self, 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(self, filename: str, chords_per_row: int = 4)
Saves the chord progression to an Excel file.
to_midi(self, filename: str, instrument: int = 1, tempo: int = 120, beats_per_chord: Union[int, list] = 2, velocity: int = 100)
Saves the chord progression to a MIDI file.
SongSection
Represents a section in a Song.
Methods
name(self)
Alias for field number 0
progression(self)
Alias for field number 1
Song
Represents a song (a series of sections).
Methods
__init__(self, sections: List[jchord.progressions.SongSection])
to_string(self, chords_per_row: int = 4, column_spacing: int = 2, newline: str = '\n')
Returns the string representation of the song.
Exceptions
InvalidProgression
Raised when encountering what seems like an invalid chord progression.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.