Core
Core jird code.
Includes representation of music, evaluation of parsed text into music, and basic functions on music.
Language
Grammar and parsing is handled by the parsing library lark. The jird grammar is:
integer: INT
ratio: integer ["/" integer]
ratio_product: ratio ("*" ratio)*
note: ratio ":" ratio_product [":" ratio]
chord: "<" [ratio_product]* ">" ":" ratio_product [":" ratio]
?atom: note | chord | "(" part ")"
?mult_expr: [ratio "*"]* atom
?pow_expr: mult_expr | mult_expr "**" ratio -> power
part: pow_expr*
music: part (";" part)*
?start: music
%import common.INT
%import common.WS
%import common.LETTER
%ignore WS
%ignore LETTER
- parse(input_string)[source]
Parse text into music.
- Parameters:
input_string (str) – String containing the musical notation as text.
- Returns:
Music corresponding to the text in input_string
- Return type:
Piece
Examples
One note.
>>> parse("7/5:1") ([Note(frequency=7/5, cents=582.512, duration=1)],)
Two notes in succession.
>>> print_music(parse("2:1/4 6/5:1/4")) ( [ Note(frequency=2, cents=1200.0, duration=1/4), Note(frequency=6/5, cents=315.641, duration=1/4), ], ),
A chord.
>>> print_music(parse("<1 7/6 16/9>:1/4")) ( [ ( Note(frequency=1, cents=0.0, duration=1/4), Note(frequency=7/6, cents=266.871, duration=1/4), Note(frequency=16/9, cents=996.09, duration=1/4), ), ], ),
Music representation
- class Power(base, exponent_numerator, exponent_denominator)[source]
Unevaluated power.
The power is of the form b**(m/n) where b, m, and n are integers. Used for example to make number of steps in equal temperaments more obvious, e.g. 2**8/12 rather than 2**2/3.
- class MidiNote(pitch, bend, ticks, velocity)[source]
Midi representation of a musical note.
- pitch
Midi pitch number. Between 0 and 127.
- Type:
int
- bend
Midi pitch bend number. Between 0 and 16383.
- Type:
int, optional
- ticks
Number of midi ticks that the note lasts.
- Type:
int
- velocity
Midi note velocity (often mapped to loudness by synths). Between 0 and 127.
- Type:
int
- class Note(frequency, duration, volume=Fraction(1, 1))[source]
Individual musical note.
A note here is characterized by its frequency (giving its pitch), its duration (giving how long it is played for), and its volume (giving how loud it sounds). Frequency, duration, and volume are given as a fraction of basic units which are left unspecified.
- frequency
Frequency ratio, e.g. 5/4. Real frequency of note is this frequency ratio multiplied by the basic frequency \(f_0\).
- Type:
RatioProduct or Fraction or Power
- duration
Duration of note as a fraction of a basic time unit \(t_0\). The basic time unit can be chosen freely, e.g. to be the length of one bar or one whole note.
- Type:
RatioProduct or Fraction
- volume
Volume of note as a fraction of a reference volume. The reference volume 1/1 is mapped to midi volume 64.
- Type:
RatioProduct or Fraction
Notes
A rest is represented by a note with zero frequency.
- property cents
Calculate number of cents in the interval from 1 to the note’s frequency.
- Returns:
Number of cents in interval from 1 to frequency rounded to three decimal places. Returns nan if the note has zero frequency (corresponding to a rest).
- Return type:
float
Examples
There are about 700 cents in a just perfect fifth.
>>> note = parse("3/2:1")[0][0] >>> note.cents 701.955
Temperament
- temper_note(note, *, edo)[source]
Temper single note.
Approximates frequency of note by one of the frequencies obtained by splitting the octave into edo parts.
- Parameters:
note (Note) – Note to temper.
edo (int) – Number of Equal Divisions of the Octave to select the tempered frequency from. For example edo = 12 is the common equal temperament. Other popular values are 19, 31, and 53, but anything is possible and perhaps interesting.
- Returns:
Closest approximation to note in a system of edo equal divisions of the octave.
- Return type:
Examples
Consider a minor third (frequency ratio 6/5):
>>> note = parse("6/5:1")[0][0] >>> note Note(frequency=6/5, cents=315.641, duration=1)
Tempering with twelve notes gives three ordinary semitones, exactly 300 cents.
>>> temper_note(note, edo=12) Note(frequency=2**3/12, cents=300.0, duration=1)
Tempering with nineteen notes gives a very close approximation to the just frequency.
>>> temper_note(note, edo=19) Note(frequency=2**5/19, cents=315.789, duration=1)
Notes
A ratio product has each term tempered separately (rather than first multiplying terms then tempering). This is to preserve the tempered intervals within a chord when its frequency is multiplied. For example <1 5/4 11/8>:1 is tempered in 12EDO to 0, 4, 6 steps, but 80/81*<1 5/4 11/8>:1 would be tempered to 0, 4, 5 steps. From listening to automatically tempered just intonation music (e.g. megamorsel in music dir in this repo), this shifting of chord quality can sound strange. Tempering each term in a product separately makes sure that the intervals within the chord remain unchanged by multiplication.
Interval tables
- print_interval_table(music)[source]
Print interval table built from all frequencies in music.
- Parameters:
music (Music) – Music containing frequencies to build intervals from.
Examples
>>> print_interval_table(parse("<1 7/6 4/3>:1")) 1 7/6 4/3 --------------- 1 | 1 7/6 4/3 7/6 | 6/7 1 8/7 4/3 | 3/4 7/8 1
- interval_table(frequencies)[source]
Find interval between each pair of frequencies in frequencies.
- Parameters:
frequencies (iterable of Ratio) – Frequencies to find intervals between.
- Returns:
All intervals between frequencies.
- Return type:
list of list of Ratio
Examples
>>> table = interval_table([Fraction(1), Fraction(5, 4), Fraction(3, 2)]) >>> for row in table: print(row) [Fraction(1, 1), Fraction(5, 4), Fraction(3, 2)] [Fraction(4, 5), Fraction(1, 1), Fraction(6, 5)] [Fraction(2, 3), Fraction(5, 6), Fraction(1, 1)]
Useful functions
- all_frequencies(music)[source]
Find all frequencies in music.
- Parameters:
music (Music) – Music to find frequencies in.
- Returns:
List of all frequencies in music sorted from lowest to highest.
- Return type:
list of [Fraction or float]
Examples
Any products are evaluated so frequencies are given in their simplest form.
>>> music = parse("1:1 5/4:1 4/3*5/4:1") >>> all_frequencies(music) [Fraction(1, 1), Fraction(5, 4), Fraction(5, 3)]
This can be used to find a scale in which all notes in music can be found.
>>> music = parse("10/9*<1 6/5 9/5>:1 3/2*<1 5/4>:1") >>> all_frequencies(music) [Fraction(10, 9), Fraction(4, 3), Fraction(3, 2), Fraction(15, 8), Fraction(2, 1)]
- total_duration(music)[source]
Find total duration of music.
Takes largest duration if music contains simultaneous pieces of different duration.
- Parameters:
music (Music) – Music to find duration of.
- Returns:
Total duration of music.
- Return type:
Number
Examples
>>> music = parse("7/6:1/4 4/3:1/4") >>> total_duration(music) Fraction(1, 2)
>>> music = parse("2:1 3/2:1; 1/2:2") >>> total_duration(music) Fraction(2, 1)
- height(music)[source]
Measure of number of simultaneous notes in music.
Used for example to assign enough midi channels to each part in
jird.midi.music_to_midi_file()
.- Parameters:
music (Music) – Music to measure height of.
- Returns:
Measure of simultaneous height of music.
- Return type:
int
Examples
>>> height(parse("<1 5/4 3/2 7/4>:1")) 4
>>> height(parse("<1 5/4 3/2 7/4>:1; 1/2:1")) 5
>>> height(parse("1:1/4 9/8:1/4 5/4:1/2; 1/2:1/2 3/4:1/2")) 2
- lowest(music)[source]
Find lowest frequency in music.
- Parameters:
music (Music) – Music to containing frequencies to consider.
- Returns:
Lowest frequency in music.
- Return type:
Number
- print_music(music, level=0)[source]
Pretty print music.
- Parameters:
music (Music) – Music to be printed.
level (int) – Indentation level. Defaults to 0. Used to indent nested pieces of music when print_music is called recursively.
- apply_to_notes(music, f)[source]
Apply a function f to each note in a Music object.
- Parameters:
music (Music) – Music containing the notes to apply f to.
f (function) – Function to apply to each note.
- Returns:
The music object with f applied to each note.
- Return type:
Any
Examples
Extract the cents for each note in some music.
>>> music = parse("5/4:1 <1 3/2>:1") >>> apply_to_notes(music, lambda x: x.cents) ([386.314, (0.0, 701.955)],)
Double duration of each note.
>>> music = parse("7/6:1 4/3:1")[0] >>> music [Note(frequency=7/6, cents=266.871, duration=1), Note(frequency=4/3, cents=498.045, duration=1)]
>>> f = lambda x: Note(frequency=x.frequency, duration=evaluate(Fraction(2) * x.duration)) >>> apply_to_notes(music, f) [Note(frequency=7/6, cents=266.871, duration=2), Note(frequency=4/3, cents=498.045, duration=2)]
- evaluate(quantity)[source]
Evaluate a quantity.
For quantities which are Unevaluated call their evaluate to force evaluation.
- Parameters:
quantity (Unevaluated or Number) – quantity to evaluate.
- Returns:
Evaluated quantity
- Return type:
Number
Examples
>>> quantity = RatioProduct((Fraction(5, 4), Fraction(6, 5))) >>> quantity 5/4*6/5 >>> evaluate(quantity) Fraction(3, 2)
>>> evaluate(Fraction(7, 6)) Fraction(7, 6)
Transformation from AST to Music
- class NoteTransformer(visit_tokens=True)[source]
Transformer to convert an abstract syntax tree (AST) into a Music object.
Each method is called on the corresponding node in the AST, so methods have the same names as elements of the grammar. The transformer works up the tree from the bottom. For more information see the Lark docs here.
- NoteTransformer.integer(children)[source]
Convert token (subclass of string) for integer to actual integer.
- Parameters:
children (list of Token) – List of exactly one string representing an integer.
- Returns:
Integer corresponding to single element of children.
- Return type:
int
- NoteTransformer.ratio(children)[source]
Convert pair of integers representing a ratio into the actual ratio.
The ratio is returned as a RatioProduct in order to leave any later multiplications in a factored form, e.g. 5/4*3/2 will remain 5/4*3/2 rather than 15/8. This is because the factorization often comes from ‘factoring out the root note’ and so is musically useful to know.
- Parameters:
children (list of (int or None)) – The numerator and denominator of the ratio. If the denominator is None the numerator is returned. This allows handling both “3/2” and “2” as ratios.
- Returns:
Ratio corresponding to integers in children.
- Return type:
- NoteTransformer.ratio_product(children)[source]
Form product of ratios.
- Parameters:
children (list of RatioProduct) – Ratios to multiply.
- Returns:
Product of ratios in children.
- Return type:
- NoteTransformer.mult_expr(children)[source]
Multiply frequencies of notes in music by a ratio.
- Parameters:
children (list of RatioProduct or Music) – Last element is the Music containing the notes to multiply the frequencies of. Preceding elements are ratios to multiply frequencies in the music by.
- Returns:
Music with its note frequencies multiplied by given ratios.
- Return type:
Music
- NoteTransformer.power(children)[source]
Multiply note volume by exponent.
- Parameters:
children (list of Music or RatioProduct) – Two element list of music and exponent.
- Returns:
Music with its volume multiplied.
- Return type:
Music
- NoteTransformer.note(children)[source]
Convert frequency, duration, and volume into corresponding note.
- Parameters:
children (list of RatioProduct) – Three element list containing frequency, duration, and volume of a note. Volume can be None.
- Returns:
List containing a single Note of given frequency, duration, and volume. Volume is defaulted to one.
- Return type:
list of Note
- NoteTransformer.chord(children)[source]
Convert list of frequencies, duration, and volume into the corresponding chord.
- Parameters:
children (list of RatioProduct) – List containing the frequencies of the notes in the chord along with the duration of the chord as the second last element and the volume as the last element. Volume can be None.
- Returns:
List containing a single tuple of notes making up the given chord.
- Return type:
list of tuple of Note