/modest

musical harmony library for Lua

Primary LanguageFennelMozilla Public License 2.0MPL-2.0

Modest

Musical harmony library for Lua.

Installation

The easiest way to install Modest is via LuaRocks.

luarocks install modest-harmony

Features

  • Chord Object. Supports a wide range of chords, from simple major/minor to complex jazz chords. Can transpose chords and retrieve individual notes. Provides a flexible string parsing.
  • 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.

General Information

Lua version support

  • The library supports both Lua 5.4 and LuaJIT. It should also be compatible with older Lua 5.x versions.

Immutability

  • 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.

String parsing

  • 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.
    local 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).

Metamethods

  • 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 string concatenation or when using the ‘print’ function. It uses Unicode symbols for accidentals.

Documentation

Chord

Methods

  • fromstring(string) -> Chord
    • Parses a string and returns a Chord object.
    • Example:
      local chord = Chord.fromstring("Cmaj7")
      print(chord)
              
      CM7
              
  • transpose(self, interval) -> Chord
    • Returns a new Chord transposed by the given interval.
    • Example:
      local transposed = Chord.fromstring("C6/9"):transpose("m3")
      print(transposed)
              
      E♭6/9
              
  • transpose_down(self, interval) -> Chord
    • Similar to transpose, returns a chord transposed down by the given interval.
    • Example:
      local transposed_down = Chord.fromstring("Ab9"):transpose_down("P5")
      print(transposed_down)
              
      D♭9
              
  • notes(self, octave=nil) -> [Note]
    • Returns the notes that make up the chord. Optionally, specify the octave of the root note.
    • Example:
      local notes = Chord.fromstring("F#"):notes(4)
      for _, note in ipairs(notes) do print(note) end
              
      F♯4
      A♯4
      C♯5
              
  • numeric(self) -> [int]
    • Converts the chord into a numeric representation, with each note represented as the number of semitones from the C of the chord’s root octave.
    • Examples:
      local numeric = Chord.fromstring("C/Bb"):numeric()
      print(table.concat(numeric, ", "))
              
      -2, 0, 4, 7
              
      local numeric = Chord.fromstring("G9"):numeric()
      print(table.concat(numeric, ", "))
              
      7, 11, 14, 17, 21
              
  • tostring(self, ascii=nil) -> string
    • Converts the chord into a string. By default accidental will be represented with special Unicode characters. Pass a true value as a parameter to get an ASCII representation.
    • Example:
      local chord = Chord.fromstring("C#maj7")
      print(chord:tostring())
      print(chord:tostring(true))
              
      C♯M7
      C#M7
              
  • toascii(self) -> string
    • Shorthand for chord:tostring(true). Returns the chord as a string with ASCII representations for accidentals.
    • Example:
      local chord = Chord.fromstring("G7#11")
      print(chord:toascii())
              
      G7(#11)
              

Interval

Methods

  • fromstring(string) -> Interval
    • Parses a string and returns an Interval object. Examples:
      • “m3” = minor third
      • “P4” = perfect fourth
      • “A5” = augmented fifth
      • “d7” = diminished seventh
      • “M6” = major sixth.
    • Example:
      local interval = Interval.fromstring("P4")
      print(interval)
              
      P4
              
  • 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:
      local interval = Interval.new(3, "aug")
      print(interval)
              
      A3
              
      local interval = Interval.new(13, "maj")
      print(interval)
              
      M13
              
      local interval = Interval.new(5)
      print(interval)
              
      P5
              
      local _, err = pcall(function() Interval.new(5, "min") end)
      print(err)
              
      /usr/local/share/lua/5.4/modest/basics.lua:134: Invalid combination of size and quality
              
  • identify(note1, note2) -> Interval
    • Identifies the interval between two notes.
    • Example:
      local interval = Interval.identify("C", "F")
      print(interval)
              
      P4
              
  • semitones(self) -> int
    • Returns the number of semitones in the interval.
    • Examples:
      local semitones = Interval.fromstring("M3"):semitones()
      print(semitones)
              
      4
              
  • tostring(self) -> string
    • Converts the interval into a string representation.
    • Example:
      local interval = Interval.new(6, "min"):tostring()
      print(interval)
              
      m6
              

Note

Methods

  • fromstring(string) -> Note
    • Parses a string and returns a Note object.
    • Examples:
      local note = Note.fromstring("C#4")
      print(note)
              
      C♯4
              
      local note = Note.fromstring("E") -- the octave is optional
      print(note)
              
      E
              
  • 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:
      local note = Note.new("D", 1, 5)
      print(note)
              
      D♯5
              
      local note = Note.new("B", -2)
      print(note)
              
      B𝄫
              
  • transpose(self, interval) -> Note
    • Returns a new note transposed by the given interval.
    • Example:
      local transposed = Note.fromstring("C4"):transpose("P4")
      print(transposed)
              
      F4
              
  • transpose_down(self, interval) -> Note
    • Returns a new note transposed down by the given interval.
    • Example:
      local transposed_down = Note.fromstring("C4"):transpose_down("m3")
      print(transposed_down)
              
      A3
              
  • pitch_class(self) -> int
    • Returns a number from 0 to 11 representing the pitch class of the note (e.g., C=0, C♯/D♭=1, …, B=11).
    • Example:
      local note = Note.fromstring("G")
      print(note:pitch_class())
              
      7
              
  • tostring(self, ascii) -> string
  • toascii(self) -> string
    • Works similarly to the Chord methods of the same name.
    • Example:
      local note = Note.fromstring("D#4")
      print(note:tostring())
      print(note:tostring(true))
      print(note:toascii())
              
      D♯4
      D#4
      D#4
              

Similar libraries in other languages