Skip to content

Commit

Permalink
Merge 27ac295 into 8db1fe6
Browse files Browse the repository at this point in the history
  • Loading branch information
albertms10 committed Mar 30, 2024
2 parents 8db1fe6 + 27ac295 commit 5e0ca28
Show file tree
Hide file tree
Showing 5 changed files with 375 additions and 28 deletions.
247 changes: 230 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@
[![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/license/bsd-3-clause/)
[![style: very good analysis](https://img.shields.io/badge/style-very_good_analysis-B22C89.svg)](https://pub.dev/packages/very_good_analysis)

A simple Dart library that provides a comprehensive set of utilities
for working with music theory concepts through a beautifully crafted API.
A comprehensive Dart library for effortlessly working with music theory concepts,
offering an elegant and beautifully crafted API.

## Features

- Chords, harmonic functions, and circle of fifths
- Intervals and qualities
- Notes, frequencies, accidentals, and enharmonic operations
- Scales and scale degrees
- Notes, accidentals, and enharmonic operations
- Intervals, qualities, and circle of fifths
- Chords, scales, harmonic functions, inversions and retrogrades
- Keys, key signatures, and modes
- Tuning systems (_work in progress_)
- Frequencies and tuning systems (_work in progress_)

## Usage

Expand All @@ -32,15 +31,27 @@ For more detailed usage instructions and examples, please refer to the

### Notes

Define `Note`s from the musical scale:
Define a `Note` from a `BaseNote` and an `Accidental`, or using their
shorthand static constants:

```dart
const Note(BaseNote.e, Accidental.flat); // E♭
Note.c; // C
Note.d; // D
Note.f; // F
```

Alter them:
`BaseNote`s can be obtained from semitones or ordinal:

```dart
BaseNote.fromSemitones(2); // D
BaseNote.fromSemitones(9); // A
BaseNote.fromOrdinal(3); // E
BaseNote.fromOrdinal(7); // B
```

Alter a `Note` with `sharp` or `flat`:

```dart
Note.c.sharp; // C♯
Expand All @@ -56,14 +67,96 @@ Note.f.inOctave(4); // F4
Note.b.flat.inOctave(5); // B♭5
```

Or just parse them in both scientific and Helmholtz notations:
Or parse them in both scientific and Helmholtz notations:

```dart
Note.parse('a#'); // A♯
Pitch.parse("g''"); // G5
Pitch.parse('Eb3'); // E♭3
```

Get their difference in semitones:

```dart
BaseNote.c.difference(BaseNote.e); // 4
BaseNote.a.difference(BaseNote.e); // -5
BaseNote.a.positiveDifference(BaseNote.e); // 7
Note.c.difference(Note.e.flat); // 3
Pitch.parse('C').difference(Pitch.parse("c''''")); // 60
```

Transpose them:

```dart
Note.g.flat.transposeBy(-Interval.m3); // E♭
Note.b.inOctave(3).transposeBy(Interval.P5); // F♯4
```

Respell them by any criteria:

```dart
Note.c.sharp.respellByBaseNote(BaseNote.d); // D♭
Note.e.flat.respellByAccidental(Accidental.sharp); // D♯
Note.g.flat.inOctave(3).respellByOrdinalDistance(-1); // F♯3
Note.g.sharp.respelledUpwards; // A♭
Note.a.flat.respelledDownwards; // G♯
Note.b.sharp.inOctave(4).respelledSimple; // C5
```

Compare two `Pitch`es based on their semitones:

```dart
Note.c.inOctave(4) < Note.c.inOctave(5); // true
Note.d.inOctave(3) > Note.f.inOctave(4); // false
Note.a.flat.inOctave(5) >= Note.g.sharp.inOctave(5); // true
```

Know whether two `Note`s or `Pitch`es are enharmonically equivalent:

```dart
Note.f.sharp.isEnharmonicWith(Note.g.flat); // true
Note.c.inOctave(4).isEnharmonicWith(Note.b.sharp.inOctave(3)); // true
Note.a.isEnharmonicWith(Note.b.flat); // false
```

Represent them as [`PitchClass`es](https://en.wikipedia.org/wiki/Pitch_class):

```dart
Note.d.flat.toClass(); // {C♯|D♭}
Note.a.inOctave(4).toClass(); // {A}
```

Perform [`PitchClass` multiplications (modulo 12)](<https://en.wikipedia.org/wiki/Multiplication_(music)#Pitch-class_multiplication_modulo_12>):

```dart
PitchClass.cSharp * 7; // {G}
PitchClass.d * 7; // {D}
// observe one semitone upwards results in ascending fifths G -> D.
PitchClass.cSharp * 5; // {F}
PitchClass.d * 5; // {A♯|B♭}
// observe one semitone upwards results in ascending fourths F -> B-flat.
```

Represent them using any notation system:

```dart
Note.d.flat
..toString() // D♭
..toString(system: NoteNotation.romance) // Re♭
..toString(system: NoteNotation.german); // Des
Note.b.flat.inOctave(-1).toString(); // B♭-1
Note.c.inOctave(6).toString(system: PitchNotation.helmholtz); // c′′′
PitchClass.c.toString(); // {C}
PitchClass.dSharp.toString(); // {D♯|E♭}
PitchClass.f.toString(system: PitchClassNotation.integer); // 5
PitchClass.aSharp.toString(system: PitchClassNotation.integer); // t
```

### Intervals

Create an `Interval`:
Expand All @@ -75,7 +168,15 @@ Size.sixth.augmented; // A6
Size.eleventh.simple.perfect; // P4
```

Or turn it descending:
Or parse it from a string:

```dart
Interval.parse('m3'); // m3
Interval.parse('P-5'); // P-5
Interval.parse('AA6'); // AA6
```

Turn it descending:

```dart
-Interval.m7; // m-7
Expand All @@ -86,8 +187,10 @@ Calculate the `Interval` between two notes:

```dart
Note.c.interval(Note.g); // P5
Note.d.interval(Note.f.sharp).inverted; // m6
Note.g.flat.transposeBy(-Interval.m3); // E♭
Note.d.interval(Note.f.sharp).inversion; // m6
BaseNote.d.intervalSize(BaseNote.f); // 3
BaseNote.a.intervalSize(BaseNote.e); // 5
```

And even play with the circle of fifths or any circle of intervals
Expand All @@ -98,6 +201,52 @@ Interval.P5.circleFrom(Note.c, distance: 12).toList();
// [C, G, D, A, E, B, F♯, C♯, G♯, D♯, A♯, E♯, B♯]
Note.c.circleOfFifths();
// (flats: [F, B♭, E♭, A♭, D♭, G♭], sharps: [G, D, A, E, B, F♯])
Note.c.flatCircleOfFifths(distance: 3); // [E♭, B♭, F, C, G, D, A]
Note.d.circleOfFifthsDistance; // 2
Note.a.flat.circleOfFifthsDistance; // -4
Note.c.fifthsDistanceWith(Note.e.flat); // -3
Note.b.fifthsDistanceWith(Note.f.sharp); // 1
```

Know whether two `Interval`s are enharmonically equivalent:

```dart
Interval.M3.isEnharmonicWith(Interval.d4); // true
Interval.A4.isEnharmonicWith(Interval.d5); // true
Interval.P1.isEnharmonicWith(Interval.m2); // false
```

Represent them as [`IntervalClass`es](https://en.wikipedia.org/wiki/Interval_class):

```dart
Interval.M2.toClass(); // {M2|d3}
Interval.m6.toClass(); // {M3|d4}
Interval.P8.toClass(); // {P1}
```

Add, subtract and multiply `IntervalClass`es:

```dart
IntervalClass.tritone + IntervalClass.M2; // {M3|d4}
IntervalClass.M3 + IntervalClass.P4; // {m3}
IntervalClass.P4 - IntervalClass.m3; // {M2|d3}
IntervalClass.P1 - IntervalClass.m2; // {m2}
IntervalClass.P4 * -1; // {P4}
IntervalClass.M2 * 0; // {P1}
IntervalClass.m3 * 2; // {A4|d5}
```

Represent them as a string:

```dart
Interval.m2.toString(); // m2
Interval.A6.toString(); // A6
IntervalClass.M2.toString(); // {M2|d3}
IntervalClass.P4.toString(); // {P4}
IntervalClass.tritone.toString(); // {A4|d5}
```

### Keys
Expand All @@ -116,11 +265,29 @@ Note.d.major.signature; // 2 (F♯ C♯)
Note.e.flat.minor.signature; // -6 (B♭ E♭ A♭ D♭ G♭ C♭)
```

And its relative `Key`:
Whether it is theoretical:

```dart
Note.e.major.isTheoretical; // false
Note.a.flat.minor.isTheoretical; // true
```

And its relative and parallel `Key`s:

```dart
Note.d.major.relative; // B minor
Note.c.minor.relative; // E♭ major
Note.f.minor.parallel; // F major
Note.c.sharp.major.parallel; // C♯ minor
```

Represent it using any notation system:

```dart
Note.d.flat.major.toString(); // D♭ major
Note.c.major.toString(system: NoteNotation.romance); // Do maggiore
Note.e.flat.minor.toString(system: NoteNotation.german); // es-moll
```

### Key signatures
Expand All @@ -133,6 +300,14 @@ KeySignature([Note.b.flat, Note.e.flat]); // -2 (B♭ E♭)
KeySignature([Note.g.sharp, Note.a.sharp]); // null (G♯ A♯)
```

Increment them by sharps or flats:

```dart
KeySignature.fromDistance(-4).incrementBy(-1); // -3 (B♭ E♭ A♭)
KeySignature([Note.f.sharp, Note.c.sharp]).incrementBy(3);
// 5 (F♯ C♯ G♯ D♯ A♯)
```

And know its `Key`s:

```dart
Expand Down Expand Up @@ -201,6 +376,23 @@ Note.c.major.scale.functionChord(
); // D maj. (D F♯ A)
```

Rearrange any set of `Note`s, `Pitch`es or `PitchClass`es
as `inversion` or `retrograde`:

```dart
({Note.b, Note.a.sharp, Note.d}).inversion.toSet(); // {B, C, G♯}
({PitchClass.dSharp, PitchClass.g, PitchClass.fSharp}).retrograde.toSet();
// {{F♯|G♭}, {G}, {D♯|E♭}}
```

Or play with its numeric representation:

```dart
({PitchClass.b, PitchClass.aSharp, PitchClass.d, PitchClass.e})
..numericRepresentation.toSet() // {0, 11, 3, 5}
..deltaNumericRepresentation.toList(); // [0, -1, 4, 2]
```

### Chords

Create a `Chord` from a series of `Note`s or a `ChordPattern`:
Expand Down Expand Up @@ -233,18 +425,24 @@ Get the `Frequency` of a `Pitch`:

```dart
Note.a.inOctave(4).frequency(); // 440
final tuningSystem =
EqualTemperament.edo12(referencePitch: Note.c.inOctave(4));
Note.b.flat.inOctave(4).frequency(
referenceFrequency: const Frequency(256),
tuningSystem:
EqualTemperament.edo12(referencePitch: Note.c.inOctave(4)),
tuningSystem: tuningSystem,
); // 456.1401436878537
Note.a.inOctave(4).frequency(temperature: const Celsius(18));
// 438.4619866006409
```

Get the closest note from a given `Frequency`:
Get the closest `Pitch` from a given `Frequency`:

```dart
const Frequency(432).closestPitch(); // A4-32
const Frequency(314).closestPitch(); // E♭4+16
const Frequency(440).closestPitch(temperature: const Celsius(24)); // A4-12
```

And combining both methods, the harmonic series of a given `Pitch`:
Expand All @@ -255,6 +453,21 @@ Note.c.inOctave(1).harmonics(upToIndex: 15);
// E4-14, F♯4-49, G4+2, A♭4+41, A♯4-31, B4-12, C5}
```

Create a `ClosestPitch` by adding or subtracting `Cent`s to a `Pitch`:

```dart
Note.f.sharp.inOctave(4) + const Cent(16); // F♯4+16
Note.g.flat.inOctave(5) - const Cent(8.236); // G♭5-8
```

Or parse a `ClosestPitch` from a string:

```dart
ClosestPitch.parse('A4'); // A4
ClosestPitch.parse('A4+12.4'); // A4+12.4
ClosestPitch.parse('E♭3-28'); // E♭3-28
```

### In a nutshell

```dart
Expand Down
Loading

0 comments on commit 5e0ca28

Please sign in to comment.