Rand Stats




Music::Helpers - Abstractions for handling musical content


use Music::Helpers;

my $mode = Mode.new(:root(C), :mode('major'))

# prints 'C4 E4 G4 ==> C maj (inversion: 0)'
say $mode.tonic.Str;

# prints 'F4 A4 C5 ==> F maj (inversion: 0)'
say $mode.next-chord($mode.tonic, intervals => [P4]).Str;


This module provides a few OO abstraction for handling musical content. Explicitly these are the classes Mode, Chord and Note as well as Enums NoteName and Interval. As anyone with even passing musical knowledge knows, Modes and Chords consists of Notes with one of those being the root and the others having a specific half-step distance from this root. As the main purpose for this module is utilizing these classes over MIDI (via Audio::PortMIDI), non-standard tunings will have to be handled by the instruments that play these notes. For convenience two enums, NoteName and Interval are exported as well. Note that the former uses only sharp notes, and uses a lower case 's' as the symbol for that, e.g:

say Cs; # works
say C#, Db, C♯, D♭; # don't work

Interval only exports from unison to octave:

# prints (P1 => 0 m2 => 1 M2 => 2 m3 => 3 M3 => 4 P4 => 5 TT => 6 P5 => 7 m6 => 8 M6 => 9 m7 => 10 M7 => 11 P8 => 12)
say Interval.enums.sort(*.value);

The arithmetic operators &infix:<+> and &infix:<-> are overloaded and exported for any combination between Notes and Intervals, and return new Notes or Intervals, depending on invocation:

my $c = Note.new(:48midi);
# $g contains 'Note.new(:43midi)'
my $g = ($c - P4);
# prints 'P4'
say $c - $g;

A Mode knows, which natural triads it contains, and memoizes the Notes and Chords on each step of the scale for probably more octaves than necessary. (That is, 10 octaves, from C-1 to C9, MIDI values 0 to 120.) Further, a Chord knows via a set of Roles applied at construction time, which kind of alterations on it are feasible. E.g:

my $mode  = Mode.new(:root(F), :mode<major>);
my $fmaj  = $mode.tonic;
my $fdom7 = $fmaj.dom7;
# prints 'F4 G4 C5 => F4 sus2 (inversion: 0)'
say $fsus2.Str;

my $mode = Mode.new(:root(F), :mode<minor>);
my $fmin = $mode.tonic;
# dies, "Method 'dom7' not found for invocant of class 'Music::Helpers::Chord+{Music::Helpers::min}'
my $fdom7 = $fmin.dom7;

Although I do readily admit that not all possible alterations and augmentations are currently implemented. A Chord tells you, which variants it support via the methods .variant-methods and .variant-roles:

my @notes = do [ Note.new(midi => $_ + 4 * P8) for C, E, G];
my $chord = Chord.new(:@notes, :0inversion);

# prints '[(sus2) (sus4) (maj6) (maj7) (dom7)]'
say $chord.variant-roles;

# prints '[sus2 sus4 maj6 maj7 dom7]'
say $chord.variant-methods;

# prints 'C4 E4 G4 B4 ==> C4 maj7 (inversion: 0)'
say $chord.variant-methods[3]($chord);

Note that .variant-methods is usually what you want to use when trying to create a variant of a given Chord.

Further, positive and negative inversions are supported via the method .invert:

# prints 'C5 F5 A5 ==> F5 maj (inversion: 2)'
say $fmaj.invert(2).Str;

# prints 'C4 F4 A4 ==> F4 maj (inversion: 2)'
say $fmaj.invert(-1).Str;

Finally, a Note knows how to build a Audio::PortMIDI::Event that can be sent via a Audio::PortMIDI::Stream, and a Chord knows to ask the Notes it consists of for these Events:

# prints a whole lot, not replicated for brevity
say $fmaj.OnEvents;

Note that this documentation is a work in progress. The file bin/example.pl6 in this repository might be of interest.