Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(harmony): make Chord implement extracted Chordable mixin methods #175

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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