Hackers News

esbudylin/modest: musical harmony library for Lua

Musical harmony library for Lua.

  • Chord Object. Supports a wide range of chords, from simple major/minor to complex jazz chords. Provides a flexible string parsing, can identify a chord based on its notes. Can transpose chords and retrieve individual notes.
  • Note Object. Handles alterations (sharps, flats, double accidentals), octaves, pitch classes.
  • Interval Object. Supports simple and compound intervals. Can identify the interval between two notes and represent it in semitones.

The easiest way to install Modest is via LuaRocks.

luarocks install modest-harmony

If you want to avoid LuaRocks, you should consider two things about the project. First, Modest depends on LPeg, a library partially written in C, which you will need to install separately. Second, the project is written in Fennel, a language that transpiles to Lua. To use Modest in your Lua code, you can either embed Fennel compiler in your project or perform ahead-of-time compilation of the Fennel files. I recommend the latter option. To do so, install Fennel and run the following command from the project’s root directory:

fennel --require-as-include --skip-include re,lpeg --compile modest/init.fnl > modest.lua

After running the command, move the resulting modest.lua file into your project and require it as you would any other Lua module.

  • The library supports both Lua 5.4 and LuaJIT. It should also be compatible with older Lua 5.x versions.
  • Methods in the library do not mutate objects; instead, they return new instances. However, as these instances are regular Lua tables, they can still be modified after creation. It is strongly advised not to mutate them, as this could lead to unexpected behavior.
  • Each object provides a ‘fromstring’ method, allowing object construction through string parsing. While Interval and Note requires strings in a strict format, Chord can parse almost any notation that may be encountered in musical scores or chord charts.

  • Any method that requires one of the library objects as an argument can also accept a string, which will be parsed using the appropriate ‘fromstring’ method. For example, both of the following expressions are valid.

    note = Note.fromstring("C5")
    note:transpose("P5")
    note:transpose(Interval.fromstring("P5"))

Representation of accidentals

  • The library supports two types of accidental representation: special Unicode symbols (‘♯’ for sharp, ‘♭’ for flat, ‘𝄪’ for double sharp, ‘𝄫’ for double flat) and ASCII characters (‘#’, ‘b’, ‘x’, ‘bb’, respectively).
  • The parsers of the Note and Chord objects can handle both types. When transforming these objects into strings, different methods are available for each representation (see below).
  • Each object implements ‘__tostring’ metamethod. In Lua, this metamethod is automatically called when an object needs to be represented as a string, such as during calls to ‘print’ or ‘tostring’ functions. The implementation uses Unicode symbols for accidentals.
  • Modest is named after Modest Petrovich Mussorgsky.
  1. Chord
    1. fromstring(string) -> Chord
    2. identify(& notes) -> Chord
    3. transpose(self, interval) -> Chord
    4. transpose_down(self, interval) -> Chord
    5. notes(self, octave=nil) -> [Note]
    6. numeric(self) -> [int]
    7. tostring(self) -> string
    8. toascii(self) -> string
  2. Interval
    1. fromstring(string) -> Interval
    2. new(size, quality=”perfect”) -> Note
    3. identify(note1, note2) -> Interval
    4. semitones(self) -> int
    5. tostring(self) -> string
  3. Note
    1. fromstring(string) -> Note
    2. new(tone, accidental=0, octave=nil) -> Note
    3. transpose(self, interval) -> Note
    4. transpose_down(self, interval) -> Note
    5. pitch_class(self) -> int
    6. tostring(self) -> string
    7. toascii(self) -> string

fromstring(string) -> Chord

  • Parses a string and returns a Chord object. Supports most of the chord types (see table below). Aims to be as flexible as possible when parsing a chord suffix, allowing various synonymous notations.

Supported chord type Examples
Basic triads C, Cm
Augmented chords Caug
Diminished and half-diminished Cdim, C⌀7, Cdim7
Suspended chords Csus2, C9sus4
Seventh chords C7, CM7, CminMaj7
Extended chords up to the 13th C9, C13
Added sixth and 6/9 chords C6, Cm(♭6), C6/9
Added tones Cadd2, Cadd9, C(♯11)
Altered chords C7♯5, C7♯5♭9
Power chords C5
Slash chords C/G

  • Example:

    CM7 = Chord.fromstring("Cmaj7")

identify(& notes) -> Chord

  • Identifies a chord based on the given notes. Accepts a variable number of string representations or Note objects. Assumes the first argument for a chord root. If the octaves of the given notes are not specified, assumes they go in ascending order. Supports the same types of chords as the ‘fromstring’ method, except for slash chords. Does not support inversions. Raises an error if the notes do not form a recognizable chord.

  • Examples:

    Cadd9 = Chord.identify("C", "E", "G", "D")
    
    -- Can also accept note objects
    Daug = Chord.identify("D", "F#", Note.fromstring("A#"))

transpose(self, interval) -> Chord

transpose_down(self, interval) -> Chord

notes(self, octave=nil) -> [Note]

fromstring(string) -> Interval

new(size, quality=”perfect”) -> Note

  • Creates a new Interval object. Size should be an integer, and quality should be a string (valid options are “dim”, “aug”, “min”, “maj”, “perfect”). The method raises an error if the interval is invalid.

  • Examples:

    A3 = Interval.new(3, "aug")
    M13 = Interval.new(13, "maj")
    P5 = Interval.new(5)
    _, err = pcall(function() Interval.new(5, "min") end)
    print(err)
    ./modest.lua:287: Invalid combination of size and quality
    

identify(note1, note2) -> Interval

fromstring(string) -> Note

new(tone, accidental=0, octave=nil) -> Note

  • Creates a new Note object. The tone should be a capital letter (e.g., “C”). The accidental should be a numeric value (e.g., -1 for flat, 1 for sharp). The octave is optional.

  • Examples:

    D_sharp_5 = Note.new("D", 1, 5)
    B_double_flat = Note.new("B", -2)

transpose(self, interval) -> Note

transpose_down(self, interval) -> Note

  • Returns a number from 0 to 11 representing the pitch class of the note (e.g., C=0, C♯/D♭=1, …, B=11).

  • Example:

    pitch_class = Note.fromstring("G"):pitch_class()
    assert(pitch_class == 7)

Similar libraries in other languages

admin

The realistic wildlife fine art paintings and prints of Jacquie Vaux begin with a deep appreciation of wildlife and the environment. Jacquie Vaux grew up in the Pacific Northwest, soon developed an appreciation for nature by observing the native wildlife of the area. Encouraged by her grandmother, she began painting the creatures she loves and has continued for the past four decades. Now a resident of Ft. Collins, CO she is an avid hiker, but always carries her camera, and is ready to capture a nature or wildlife image, to use as a reference for her fine art paintings.

Related Articles

Leave a Reply