Skip to content

Commit

Permalink
feat(harmony): make Chord implement extracted Chordable mixin met…
Browse files Browse the repository at this point in the history
…hods (#175)

* feat(harmony): make `Chord` implement extracted `Chordable` mixin methods

* docs(chord): add missing type parameter to `Chord` reference

* docs(scale): add missing type parameter in `Scale` references

* docs: add missing type parameters in references

* test(chord): add missing test cases for `add13` and `add` methods

* chore(settings): add lowercase `chordable` cSpell word
  • Loading branch information
albertms10 committed Jun 10, 2023
1 parent 2534c9f commit 27e371c
Show file tree
Hide file tree
Showing 8 changed files with 373 additions and 46 deletions.
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"cSpell.ignoreWords": ["music"],
"cSpell.words": [
"chordable",
"Chordable",
"dorian",
"helmholtz",
"heptatonic",
Expand Down
1 change: 1 addition & 0 deletions lib/music_notes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'utils/int_mod_extension.dart';
import 'utils/iterable.dart';
import 'utils/num_extension.dart';

part 'src/chordable.dart';
part 'src/enharmonic.dart';
part 'src/harmony/chord.dart';
part 'src/harmony/chord_pattern.dart';
Expand Down
44 changes: 44 additions & 0 deletions lib/src/chordable.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
part of '../music_notes.dart';

mixin Chordable<T> {
/// Returns a new [T] with an [ImperfectQuality.augmented] root triad.
T get augmented;

/// Returns a new [T] with an [ImperfectQuality.major] root triad.
T get major;

/// Returns a new [T] with an [ImperfectQuality.minor] root triad.
T get minor;

/// Returns a new [T] with an [ImperfectQuality.diminished] root triad.
T get diminished;

/// Returns a new [T] with a suspended [Interval.majorSecond].
T sus2() => add(Interval.majorSecond, replaceSizes: const [3, 4]);

/// Returns a new [T] with a suspended [Interval.perfectFourth].
T sus4() => add(Interval.perfectFourth, replaceSizes: const [2, 3]);

/// Returns a new [T] adding a [quality] 6th.
T add6([ImperfectQuality quality = ImperfectQuality.major]) =>
add(Interval.imperfect(6, quality));

/// Returns a new [T] adding a [quality] 7th.
T add7([ImperfectQuality quality = ImperfectQuality.minor]) =>
add(Interval.imperfect(7, quality));

/// Returns a new [T] adding a [quality] 9th.
T add9([ImperfectQuality quality = ImperfectQuality.major]) =>
add(Interval.imperfect(9, quality));

/// Returns a new [T] adding an [quality] 11th.
T add11([PerfectQuality quality = PerfectQuality.perfect]) =>
add(Interval.perfect(11, quality));

/// Returns a new [T] adding a [quality] 13th.
T add13([ImperfectQuality quality = ImperfectQuality.major]) =>
add(Interval.imperfect(13, quality));

/// Returns a new [T] adding [interval].
T add(Interval interval, {List<int>? replaceSizes});
}
4 changes: 2 additions & 2 deletions lib/src/enharmonic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ part of '../music_notes.dart';
@immutable
abstract interface class Enharmonic<T> implements Comparable<Enharmonic<T>> {
/// The number of semitones of the common chromatic pitch of this
/// [Enharmonic].
/// [Enharmonic<T>].
final int semitones;

/// Creates a new [Enharmonic].
/// Creates a new [Enharmonic<T>].
const Enharmonic(this.semitones);

/// Returns the different spellings sharing the same number of [semitones].
Expand Down
72 changes: 66 additions & 6 deletions lib/src/harmony/chord.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
part of '../../music_notes.dart';

class Chord<T extends Scalable<T>> implements Transposable<Chord<T>> {
/// The [Scalable<T>] items this [Chord] is built of.
class Chord<T extends Scalable<T>>
with Chordable<Chord<T>>
implements Transposable<Chord<T>> {
/// The [Scalable<T>] items this [Chord<T>] is built of.
final List<T> items;

/// Creates a new [Chord] from [items].
/// Creates a new [Chord<T>] from [items].
const Chord(this.items);

/// The root [Scalable<T>] of this [Chord].
/// The root [Scalable<T>] of this [Chord<T>].
T get root => items.first;

/// Returns the [ChordPattern] for this [Chord].
/// Returns the [ChordPattern] for this [Chord<T>].
///
/// The pattern is calculated based on the intervals between the notes rather
/// than from the root note. This approach helps differentiate compound
Expand All @@ -26,7 +28,65 @@ class Chord<T extends Scalable<T>> implements Transposable<Chord<T>> {
/// ```
ChordPattern get pattern => ChordPattern.intervalSteps(items.intervals);

/// Returns a transposed [Chord] by [interval] from this [Chord].
/// Returns the list of modifier [T]s from the root note.
///
/// Example:
/// ```dart
/// Note.a.majorTriad.add7().add9().modifiers == const [Note.g, Note.b]
/// ```
List<T> get modifiers => items.skip(3).toList();

/// Returns a new [Chord<T>] with an [ImperfectQuality.augmented] root
/// triad.
///
/// Example:
/// ```dart
/// Note.c.majorTriad.add7().augmented
/// == Chord([Note.c, Note.e, Note.g.sharp, Note.b.flat])
/// ```
@override
Chord<T> get augmented =>
Chord([...ChordPattern.augmentedTriad.on(root).items, ...modifiers]);

/// Returns a new [Chord<T>] with an [ImperfectQuality.major] root triad.
///
/// Example:
/// ```dart
/// Note.c.minorTriad.add7().major
/// == Chord([Note.c, Note.e, Note.g, Note.b.flat])
/// ```
@override
Chord<T> get major =>
Chord([...ChordPattern.majorTriad.on(root).items, ...modifiers]);

/// Returns a new [Chord<T>] with an [ImperfectQuality.minor] root triad.
///
/// Example:
/// ```dart
/// Note.c.majorTriad.add7().minor
/// == Chord([Note.c, Note.e.flat, Note.g, Note.b.flat])
/// ```
@override
Chord<T> get minor =>
Chord([...ChordPattern.minorTriad.on(root).items, ...modifiers]);

/// Returns a new [Chord<T>] with an [ImperfectQuality.diminished] root triad.
///
/// Example:
/// ```dart
/// Note.c.majorTriad.add7().diminished
/// == Chord([Note.c, Note.e.flat, Note.g.flat, Note.b.flat])
/// ```
@override
Chord<T> get diminished =>
Chord([...ChordPattern.diminishedTriad.on(root).items, ...modifiers]);

/// Returns a new [Chord<T>] adding [interval].
@override
Chord<T> add(Interval interval, {List<int>? replaceSizes}) =>
pattern.add(interval, replaceSizes: replaceSizes).on(root);

/// Returns a transposed [Chord<T>] by [interval] from this [Chord<T>].
///
/// Example:
/// ```dart
Expand Down
34 changes: 6 additions & 28 deletions lib/src/harmony/chord_pattern.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
part of '../../music_notes.dart';

class ChordPattern {
class ChordPattern with Chordable<ChordPattern> {
/// The intervals from the root note.
final List<Interval> intervals;

Expand Down Expand Up @@ -119,6 +119,7 @@ class ChordPattern {
/// Interval.minorSeventh
/// ])
/// ```
@override
ChordPattern get augmented =>
ChordPattern([...augmentedTriad.intervals, ...modifiers]);

Expand All @@ -134,6 +135,7 @@ class ChordPattern {
/// Interval.minorSeventh
/// ])
/// ```
@override
ChordPattern get major =>
ChordPattern([...majorTriad.intervals, ...modifiers]);

Expand All @@ -149,6 +151,7 @@ class ChordPattern {
/// Interval.minorSeventh
/// ])
/// ```
@override
ChordPattern get minor =>
ChordPattern([...minorTriad.intervals, ...modifiers]);

Expand All @@ -164,37 +167,12 @@ class ChordPattern {
/// Interval.minorSeventh
/// ])
/// ```
@override
ChordPattern get diminished =>
ChordPattern([...diminishedTriad.intervals, ...modifiers]);

/// Returns a new [ChordPattern] with a suspended [Interval.majorSecond].
ChordPattern sus2() => add(Interval.majorSecond, replaceSizes: const [3, 4]);

/// Returns a new [ChordPattern] with a suspended [Interval.perfectFourth].
ChordPattern sus4() =>
add(Interval.perfectFourth, replaceSizes: const [2, 3]);

/// Returns a new [ChordPattern] adding a [quality] 6th.
ChordPattern add6([ImperfectQuality quality = ImperfectQuality.major]) =>
add(Interval.imperfect(6, quality));

/// Returns a new [ChordPattern] adding a [quality] 7th.
ChordPattern add7([ImperfectQuality quality = ImperfectQuality.minor]) =>
add(Interval.imperfect(7, quality));

/// Returns a new [ChordPattern] adding a [quality] 9th.
ChordPattern add9([ImperfectQuality quality = ImperfectQuality.major]) =>
add(Interval.imperfect(9, quality));

/// Returns a new [ChordPattern] adding an [quality] 11th.
ChordPattern add11([PerfectQuality quality = PerfectQuality.perfect]) =>
add(Interval.perfect(11, quality));

/// Returns a new [ChordPattern] adding a [quality] 13th.
ChordPattern add13([ImperfectQuality quality = ImperfectQuality.major]) =>
add(Interval.imperfect(13, quality));

/// Returns a new [ChordPattern] adding [interval].
@override
ChordPattern add(Interval interval, {List<int>? replaceSizes}) {
final sizesToReplace = [interval.size, ...?replaceSizes];
final filteredIntervals = intervals.whereNot(
Expand Down
21 changes: 11 additions & 10 deletions lib/src/scale/scale.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
part of '../../music_notes.dart';

class Scale<T extends Scalable<T>> implements Transposable<Scale<T>> {
/// The [Scalable] degrees that define this [Scale].
/// The [Scalable<T>] degrees that define this [Scale<T>].
final List<T> degrees;

/// The descending [Scalable] degrees that define this [Scale] (if different).
/// The descending [Scalable<T>] degrees that define this [Scale<T>] (if
/// different).
final List<T>? _descendingDegrees;

/// Creates a new [Scale] instance from [degrees].
/// Creates a new [Scale<T>] instance from [degrees].
const Scale(this.degrees, [this._descendingDegrees]);

/// The descending [Scalable] degrees that define this [Scale].
/// The descending [Scalable<T>] degrees that define this [Scale<T>].
List<T> get descendingDegrees =>
_descendingDegrees ?? degrees.reversed.toList();

/// Returns the [ScalePattern] of this [Scale].
/// Returns the [ScalePattern] of this [Scale<T>].
///
/// Example:
/// ```dart
Expand All @@ -24,7 +25,7 @@ class Scale<T extends Scalable<T>> implements Transposable<Scale<T>> {
ScalePattern get pattern =>
ScalePattern(degrees.intervals, _descendingDegrees?.descendingIntervals);

/// Returns the reversed of this [Scale].
/// Returns the reversed of this [Scale<T>].
///
/// Example:
/// ```dart
Expand All @@ -35,7 +36,7 @@ class Scale<T extends Scalable<T>> implements Transposable<Scale<T>> {
Scale<T> get reversed =>
Scale(descendingDegrees, _descendingDegrees != null ? degrees : null);

/// Returns the [Chord] for each [ScaleDegree] of this [Scale].
/// Returns the [Chord<T>] for each [ScaleDegree] of this [Scale<T>].
///
/// Example:
/// ```dart
Expand All @@ -52,7 +53,7 @@ class Scale<T extends Scalable<T>> implements Transposable<Scale<T>> {
List<Chord<T>> get degreeChords =>
[for (var i = 1; i < degrees.length; i++) degreeChord(ScaleDegree(i))];

/// Returns the [T] for the [scaleDegree] of this [Scale].
/// Returns the [T] for the [scaleDegree] of this [Scale<T>].
///
/// Example:
/// ```dart
Expand All @@ -72,7 +73,7 @@ class Scale<T extends Scalable<T>> implements Transposable<Scale<T>> {
);
}

/// Returns the [Chord] for the [scaleDegree] of this [Scale].
/// Returns the [Chord<T>] for the [scaleDegree] of this [Scale<T>].
///
/// Example:
/// ```dart
Expand All @@ -82,7 +83,7 @@ class Scale<T extends Scalable<T>> implements Transposable<Scale<T>> {
Chord<T> degreeChord(ScaleDegree scaleDegree) =>
pattern.degreePattern(scaleDegree).on(degree(scaleDegree));

/// Returns this [Scale] transposed by [interval].
/// Returns this [Scale<T>] transposed by [interval].
///
/// Example:
/// ```dart
Expand Down
Loading

0 comments on commit 27e371c

Please sign in to comment.