A comprehensive Dart library for effortlessly working with music theory concepts,
offering an elegant and beautifully crafted API.
Features
Notes, accidentals, and enharmonic operations
Intervals, qualities, and circle of fifths
Chords, scales, harmonic functions, inversions and retrogrades
Keys, key signatures, and modes
Frequencies and tuning systems (work in progress )
Usage
Import the package into your Dart code:
import 'package:music_notes/music_notes.dart' ;
Now, you can use the provided APIs to perform various music theory operations.
For more detailed usage instructions and examples, please refer to the
API documentation .
Notes
Define a Note from a NoteName (.a to .g) and an Accidental, or using their
shorthand static constants:
const Note (.e, .flat);
Note .c;
Note .d;
Note .f;
NoteNames can be obtained from semitones or ordinal:
NoteName .fromSemitones (2 );
NoteName .fromSemitones (9 );
NoteName .fromOrdinal (3 );
NoteName .fromOrdinal (7 );
Alter a Note with sharp or flat:
Note .c.sharp;
Note .d.flat;
Note .g.flat.flat;
Note .f.sharp.sharp.sharp;
And position them in the octave, resulting in Pitches:
Note .f.inOctave (4 );
Note .b.flat.inOctave (5 );
Or parse them in both scientific and Helmholtz notations:
NoteName .parse ('b' );
Note .parse ('a#' );
Pitch .parse ("g''" );
Pitch .parse ('Eb3' );
Get their difference in semitones:
NoteName .c.difference (.e);
NoteName .a.difference (.e);
NoteName .a.positiveDifference (.e);
Note .c.difference (.e.flat);
Pitch .parse ('C' ).difference (.parse ("c''''" ));
Transpose them:
Note .g.flat.transposeBy (-Interval .m3);
Note .b.inOctave (3 ).transposeBy (.P5 );
Respell them by any criteria:
Note .c.sharp.respellByNoteName (.d);
Note .e.flat.respellByAccidental (.sharp);
Note .g.flat.inOctave (3 ).respellByOrdinalDistance (-1 );
Note .g.sharp.respelledUpwards;
Note .a.flat.respelledDownwards;
Note .b.sharp.inOctave (4 ).respelledSimple;
Compare two Pitches based on their semitones:
Note .c.inOctave (4 ) < Note .c.inOctave (5 );
Note .d.inOctave (3 ) > Note .f.inOctave (4 );
Note .a.flat.inOctave (5 ) >= Note .g.sharp.inOctave (5 );
Know whether two Notes or Pitches are enharmonically equivalent:
Note .f.sharp.isEnharmonicWith (Note .g.flat);
Note .c.inOctave (4 ).isEnharmonicWith (Note .b.sharp.inOctave (3 ));
Note .a.isEnharmonicWith (Note .b.flat);
Represent them as PitchClasses :
Note .d.flat.toClass ();
Note .a.inOctave (4 ).toClass ();
Perform PitchClass multiplications (modulo 12) :
PitchClass .cSharp * 7 ;
PitchClass .d * 7 ;
PitchClass .cSharp * 5 ;
PitchClass .d * 5 ;
Represent them using any notation formatter:
Note .d.flat
..format ()
..format (const GermanNoteNotation ())
..format (const RomanceNoteNotation .symbol ());
Note .b.flat.inOctave (-1 ).format ();
Note .c.inOctave (6 ).format (HelmholtzPitchNotation .english);
PitchClass .c.format ();
PitchClass .dSharp.format ();
PitchClass .f.format (const IntegerPitchClassNotation ());
PitchClass .aSharp.format (const IntegerPitchClassNotation ());
Intervals
Create an Interval:
const Interval .imperfect (.tenth, .major);
Interval .d5;
Size .sixth.augmented;
Size .eleventh.simple.perfect;
Or parse it from a string:
Interval .parse ('m3' );
Interval .parse ('P-5' );
Interval .parse ('AA6' );
Turn it descending:
-Interval .m7;
(-Interval .P5 ).direction;
Interval .M3 .descending;
(-Interval .P4 ).ascending;
(-Interval .P4 ).withDescending (false );
Perform common interval operations:
Interval .m3.inversion;
Interval .A4 .inversion;
Interval .m9.inversion;
Interval .m9.simple;
Interval .P11 .simple;
(-Interval .M3 ).simple;
Interval .P5 .isCompound;
Interval .M9 .isCompound;
(-Interval .P11 ).isCompound;
Interval .P5 .isDissonant;
Interval .d5.isDissonant;
Interval .M7 .isDissonant;
Respell an Interval by size:
Interval .A4 .respellBySize (.fifth);
Interval .d3.respellBySize (.second);
Calculate the Interval between two notes:
Note .c.interval (.g);
Note .d.interval (.f.sharp).inversion;
NoteName .d.intervalSize (.f);
NoteName .a.intervalSize (.e);
Know the intervallic distance between two notes:
Interval .P5 .circleDistance<Note >(from: .c, to: .d);
Interval .P4 .circleDistance<Note >(from: .b.flat, to: .d);
And even explore the circle of fifths or any circle of intervals
up to a distance:
Interval .P5 .circleFrom (Note .c).take (13 ).toList ();
Note .c.circleOfFifths (distance: 3 );
Note .c.splitCircleOfFifths.down.take (6 ).toList ();
Note .c.splitCircleOfFifths.up.take (8 ).toList ();
Note .d.circleOfFifthsDistance;
Note .a.flat.circleOfFifthsDistance;
Note .c.fifthsDistanceWith (.e.flat);
Note .b.fifthsDistanceWith (.f.sharp);
Know whether two Intervals are enharmonically equivalent:
Interval .M3 .isEnharmonicWith (Interval .d4);
Interval .A4 .isEnharmonicWith (Interval .d5);
Interval .P1 .isEnharmonicWith (Interval .m2);
Represent them as IntervalClasses :
Interval .M2 .toClass ();
Interval .m6.toClass ();
Interval .P8 .toClass ();
Compare two Intervals based on their semitones:
Interval .m3 < .P5 ;
Interval .m7 <= .P5 ;
-Interval .P4 > .M3 ;
Add, subtract and multiply Intervals and IntervalClasses:
Interval .m2 + .M2 ;
Interval .M2 + .P4 ;
IntervalClass .tritone + .M2 ;
IntervalClass .M3 + .P4 ;
IntervalClass .P4 - .m3;
IntervalClass .P4 * -1 ;
IntervalClass .M2 * 0 ;
IntervalClass .m3 * 2 ;
Represent them as a string:
Interval .m2.format ();
Interval .A6 .format ();
IntervalClass .M2 .format ();
IntervalClass .P4 .format ();
IntervalClass .tritone.format ();
Keys
Create a Key or get it from a given Note:
const Key (.e, .minor);
Note .a.flat.major;
Know its KeySignature:
Note .d.major.signature;
Note .e.flat.minor.signature;
Whether it is theoretical:
Note .e.major.isTheoretical;
Note .a.flat.minor.isTheoretical;
And its relative and parallel Keys:
Note .d.major.relative;
Note .c.minor.relative;
Note .f.minor.parallel;
Note .c.sharp.major.parallel;
Represent it using any notation formatter:
Note .d.flat.major.format ();
Note .c.major.format (const RomanceKeyNotation ());
Note .e.flat.minor.format (const GermanKeyNotation ());
Key signatures
Create a KeySignature:
KeySignature .fromDistance (4 );
KeySignature ([.b.flat, .e.flat]);
KeySignature ([.g.sharp, .a.sharp]);
Increment them by sharps or flats:
KeySignature .fromDistance (-4 ).incrementBy (-1 );
KeySignature ([.f.sharp, .c.sharp]).incrementBy (3 );
And know its Keys:
KeySignature ([.f.sharp]).keys[TonalMode .major];
KeySignature .empty.keys[TonalMode .minor];
Non-canonical key signatures are also supported, although they
return null when asked about their fifths distance or keys:
KeySignature ([.a.flat])
..isCanonical
..distance
..keys;
Modes
Get each Mode’s ScalePattern:
TonalMode .minor.scale;
ModalMode .locrian.scale;
Their Dorian Brightness Quotient :
ModalMode .lydian.brightness;
ModalMode .dorian.brightness;
ModalMode .aeolian.brightness;
Or its mirrored version:
ModalMode .ionian.mirrored;
ModalMode .aeolian.mirrored;
Scales
Create a Scale from a ScalePattern:
ScalePattern .lydian.on (Note .d);
ScalePattern .wholeTone.on (Note .f);
ScalePattern .majorPentatonic.on (Note .g.flat);
Or get it from a Key:
Note .a.flat.major.scale;
Note .d.minor.scale;
Even experiment with any ScaleDegree or HarmonicFunction:
ScalePattern .lydian.on (Note .e).degree (.iv);
Note .c.major.scale.functionChord (
HarmonicFunction .dominantV / .dominantV,
);
Rearrange any collection of Notes, Pitches or PitchClasses
as inversion or retrograde:
<Note >{.b, .a.sharp, .d}.inversion.toSet ();
<PitchClass >{.dSharp, .g, .fSharp}.retrograde.toSet ();
Or know its numeric representation:
<PitchClass >{.b, .aSharp, .d, .e}
..numericRepresentation ()
.toSet ()
..numericRepresentation (reference: .d)
.toSet ()
..deltaNumericRepresentation.toList ();
Chords
Create a Chord from a series of Notes or a ChordPattern:
Chord ([.a, .c.sharp, .e]);
ChordPattern .augmentedTriad.add11 ().add13 ().on (.d.sharp);
Or build it on top of a Note:
Note .f.minorTriad.add7 ().add9 (.minor);
Note .e.flat.diminishedTriad.add7 ().transposeBy (.m2);
Or modify its root triad:
Note .g.minorTriad.major;
Note .f.sharp.majorTriad.add9 ().diminished;
Frequencies
Get the Frequency of a Pitch:
Note .a.inOctave (4 ).frequency ();
Note .a.inOctave (4 ).frequency (temperature: const Celsius (18 ));
Create a TuningFork from a Pitch and a reference Frequency,
or using its shorthand static constants:
Note .a.inOctave (4 ).at (const Frequency (438 ));
TuningFork .a440;
TuningFork .c256;
And use it in a TuningSystem:
Note .b.flat
.inOctave (4 )
.frequency (
tuningSystem: const EqualTemperament .edo12 (fork: .c256),
);
Get the Frequency at a given temperature:
const Frequency (440 ).at (const Celsius (18 ));
const Frequency (440 ).at (const Celsius (24 ));
Get the closest Pitch from a given Frequency:
const Frequency (432 ).closestPitch ();
const Frequency (314 ).closestPitch ();
const Frequency (440 ).closestPitch (temperature: const Celsius (24 ));
And combining both frequency and closestPitch methods,
the harmonic series of a given Pitch:
Note .c.inOctave (1 ).harmonics ().take (16 ).toSet ();
Create a ClosestPitch by adding or subtracting Cents to a Pitch:
Note .f.sharp.inOctave (4 ) + const Cent (16 );
Note .g.flat.inOctave (5 ) - const Cent (8.236 );
Or parse a ClosestPitch from a string:
ClosestPitch .parse ('A4' );
ClosestPitch .parse ('E♭3-28' );
ClosestPitch .parse ('A4+12.6' ).format (
const StandardClosestPitchNotation (fractionDigits: 1 ),
);
In a nutshell
ScalePattern
.lydian
.on (Note .parse ('a' ))
.transposeBy (.M2 )
.degree (.iii)
.respelledUpwards
.major
.relative
.scale
.degreeChord (.v)
.add9 ();
Similar projects in other languages
Contributing
Contributions are welcome! If you encounter any issues or have suggestions for improvements, please feel free to open an issue or submit a pull request on the GitHub repository .
Star History
License
This package is released under the BSD-3-Clause License .