From a0dfd752f9d330e21fdb86acf3006b979a502618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Ma=C3=B1osa?= Date: Thu, 7 Mar 2024 23:50:37 +0100 Subject: [PATCH 1/2] =?UTF-8?q?refactor(scalable):=20=E2=99=BB=EF=B8=8F=20?= =?UTF-8?q?extract=20`isEnharmonicWith`=20extension=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/scalable.dart | 29 +++++++++++++++++++++++++++++ lib/src/scale/scale.dart | 12 ++++-------- lib/src/scale/scale_pattern.dart | 8 +++----- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/lib/src/scalable.dart b/lib/src/scalable.dart index 037b1b3d..6c98afd0 100644 --- a/lib/src/scalable.dart +++ b/lib/src/scalable.dart @@ -1,3 +1,5 @@ +import 'package:collection/collection.dart' show IterableEquality; + import 'interval/interval.dart'; import 'interval/interval_class.dart'; import 'music.dart'; @@ -54,6 +56,19 @@ extension ScalableIterable> on Iterable { /// The [PitchClass] representation of this [ScalableIterable]. Iterable toClass() => map((scalable) => scalable.toClass()); + /// Whether this [Iterable] is enharmonically equivalent to [other]. + /// + /// Example: + /// ```dart + /// [Note.c.sharp, Note.f, Note.a.flat] + /// .isEnharmonicWith([Note.d.flat, Note.e.sharp, Note.g.sharp]) + /// == true + /// + /// [Note.d.sharp].isEnharmonicWith([Note.a.flat]) == false + /// ``` + bool isEnharmonicWith(Iterable other) => + const IterableEquality().equals(toClass(), other.toClass()); + /// Transposes this [Iterable] by [interval]. Iterable transposeBy(Interval interval) => map((item) => item.transposeBy(interval)); @@ -114,4 +129,18 @@ extension ScalableIterable> on Iterable { extension IntervalIterable on Iterable { /// The [PitchClass] representation of this [IntervalIterable]. Iterable toClass() => map((interval) => interval.toClass()); + + /// Whether this [Iterable] is enharmonically equivalent to [other]. + /// + /// Example: + /// ```dart + /// const [Interval.m2, Interval.m3, Interval.M2] + /// .isEnharmonicWith(const [Interval.m2, Interval.A2, Interval.d3]) + /// == true + /// + /// const [Interval.m2].isEnharmonicWith(const [Interval.P4]) == false + /// ``` + bool isEnharmonicWith(Iterable other) => + const IterableEquality() + .equals(toClass(), other.toClass()); } diff --git a/lib/src/scale/scale.dart b/lib/src/scale/scale.dart index f3633eb5..b9d3b891 100644 --- a/lib/src/scale/scale.dart +++ b/lib/src/scale/scale.dart @@ -1,5 +1,5 @@ import 'package:collection/collection.dart' - show IterableEquality, ListEquality, UnmodifiableListView; + show ListEquality, UnmodifiableListView; import 'package:meta/meta.dart' show immutable; import '../harmony/chord.dart'; @@ -7,7 +7,6 @@ import '../harmony/harmonic_function.dart'; import '../interval/interval.dart'; import '../interval/quality.dart'; import '../interval/size.dart'; -import '../note/pitch_class.dart'; import '../scalable.dart'; import '../transposable.dart'; import 'scale_degree.dart'; @@ -155,12 +154,9 @@ class Scale> implements Transposable> { /// == true /// ``` bool isEnharmonicWith(Scale other) => - const IterableEquality() - .equals(_degrees.toClass(), other._degrees.toClass()) && - const IterableEquality().equals( - (_descendingDegrees ?? const []).toClass(), - (other._descendingDegrees ?? const []).toClass(), - ); + _degrees.isEnharmonicWith(other._degrees) && + (_descendingDegrees ?? const []) + .isEnharmonicWith(other._descendingDegrees ?? const []); /// Transposes this [Scale] by [interval]. /// diff --git a/lib/src/scale/scale_pattern.dart b/lib/src/scale/scale_pattern.dart index 2939140a..d6c09768 100644 --- a/lib/src/scale/scale_pattern.dart +++ b/lib/src/scale/scale_pattern.dart @@ -362,11 +362,9 @@ final class ScalePattern { /// == true /// ``` bool isEnharmonicWith(ScalePattern other) => - const IterableEquality() - .equals(_intervalSteps.toClass(), other._intervalSteps.toClass()) && - const IterableEquality().equals( - (_descendingIntervalSteps ?? const []).toClass(), - (other._descendingIntervalSteps ?? const []).toClass(), + _intervalSteps.isEnharmonicWith(other._intervalSteps) && + (_descendingIntervalSteps ?? const []).isEnharmonicWith( + other._descendingIntervalSteps ?? const [], ); /// The name associated with this [ScalePattern]. From df9c8cc59b364a96e0c3378716eecd12a414dc25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Ma=C3=B1osa?= Date: Fri, 8 Mar 2024 00:23:37 +0100 Subject: [PATCH 2/2] =?UTF-8?q?refactor(class=5Fmixin):=20=E2=99=BB?= =?UTF-8?q?=EF=B8=8F=20extract=20common=20members=20to=20`ClassMixin`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/class_mixin.dart | 50 ++++++++++++++++++++++++++++++++ lib/src/interval/interval.dart | 7 ++++- lib/src/note/note.dart | 10 ------- lib/src/scalable.dart | 48 ++++-------------------------- lib/src/scale/scale.dart | 3 ++ lib/src/scale/scale_pattern.dart | 9 +++--- 6 files changed, 69 insertions(+), 58 deletions(-) create mode 100644 lib/src/class_mixin.dart diff --git a/lib/src/class_mixin.dart b/lib/src/class_mixin.dart new file mode 100644 index 00000000..0fef7716 --- /dev/null +++ b/lib/src/class_mixin.dart @@ -0,0 +1,50 @@ +import 'package:collection/collection.dart' show IterableEquality; + +/// A Class mixin. +mixin ClassMixin { + /// The number of semitones that define this [Class]. + int get semitones; + + /// Creates a new [Class] from [semitones]. + Class toClass(); + + /// Whether [Class] is enharmonically equivalent to [other]. + /// + /// See [Enharmonic equivalence](https://en.wikipedia.org/wiki/Enharmonic_equivalence). + /// + /// Example: + /// ```dart + /// Note.g.sharp.isEnharmonicWith(Note.a.flat) == true + /// Note.c.isEnharmonicWith(Note.b.sharp) == true + /// Note.e.isEnharmonicWith(Note.f) == false + /// ``` + bool isEnharmonicWith(ClassMixin other) => + toClass() == other.toClass(); +} + +/// A Class iterable. +extension ClassIterable on Iterable> { + /// The [Class] representation of this [Iterable]. + Iterable toClass() => map((interval) => interval.toClass()); + + /// Whether this [Iterable] is enharmonically equivalent to [other]. + /// + /// See [Enharmonic equivalence](https://en.wikipedia.org/wiki/Enharmonic_equivalence). + /// + /// Example: + /// ```dart + /// [Note.c.sharp, Note.f, Note.a.flat] + /// .isEnharmonicWith([Note.d.flat, Note.e.sharp, Note.g.sharp]) + /// == true + /// + /// [Note.d.sharp].isEnharmonicWith([Note.a.flat]) == false + /// + /// const [Interval.m2, Interval.m3, Interval.M2] + /// .isEnharmonicWith(const [Interval.m2, Interval.A2, Interval.d3]) + /// == true + /// + /// const [Interval.m2].isEnharmonicWith(const [Interval.P4]) == false + /// ``` + bool isEnharmonicWith(Iterable> other) => + IterableEquality().equals(toClass(), other.toClass()); +} diff --git a/lib/src/interval/interval.dart b/lib/src/interval/interval.dart index f6acd5b4..31ad8eda 100644 --- a/lib/src/interval/interval.dart +++ b/lib/src/interval/interval.dart @@ -3,6 +3,7 @@ import 'package:meta/meta.dart' show immutable; import 'package:music_notes/utils.dart'; +import '../class_mixin.dart'; import '../note/note.dart'; import '../scalable.dart'; import 'interval_class.dart'; @@ -16,7 +17,9 @@ import 'size.dart'; /// * [Quality]. /// * [IntervalClass]. @immutable -final class Interval implements Comparable { +final class Interval + with ClassMixin + implements Comparable { /// Number of lines and spaces (or alphabet letters) spanning the two notes, /// including the beginning and end. final Size size; @@ -221,6 +224,7 @@ final class Interval implements Comparable { /// Interval.A4.semitones == 6 /// (-Interval.M3).semitones == -4 /// ``` + @override int get semitones => (size.semitones.abs() + quality.semitones) * size.sign; /// Whether this [Interval] is descending. @@ -391,6 +395,7 @@ final class Interval implements Comparable { /// Interval.d4.toClass() == IntervalClass.M3 /// Interval.P8.toClass() == IntervalClass.P1 /// ``` + @override IntervalClass toClass() => IntervalClass(semitones); /// The string representation of this [Interval] based on [system]. diff --git a/lib/src/note/note.dart b/lib/src/note/note.dart index 9785f6ad..a605e55c 100644 --- a/lib/src/note/note.dart +++ b/lib/src/note/note.dart @@ -278,16 +278,6 @@ final class Note extends Scalable implements Comparable { respellByAccidental(Accidental.natural) ?? respellByAccidental(Accidental(accidental.semitones.sign))!; - /// Whether this [Note] is enharmonically equivalent to [other]. - /// - /// Example: - /// ```dart - /// Note.g.sharp.isEnharmonicWith(Note.a.flat) == true - /// Note.c.isEnharmonicWith(Note.b.sharp) == true - /// Note.e.isEnharmonicWith(Note.f) == false - /// ``` - bool isEnharmonicWith(Note other) => toPitchClass() == other.toPitchClass(); - /// This [Note] positioned in the given [octave] as a [Pitch]. /// /// Example: diff --git a/lib/src/scalable.dart b/lib/src/scalable.dart index 6c98afd0..d518ee8a 100644 --- a/lib/src/scalable.dart +++ b/lib/src/scalable.dart @@ -1,19 +1,16 @@ -import 'package:collection/collection.dart' show IterableEquality; - +import 'class_mixin.dart'; import 'interval/interval.dart'; -import 'interval/interval_class.dart'; import 'music.dart'; import 'note/pitch_class.dart'; import 'transposable.dart'; /// A interface for items that can form scales. -abstract class Scalable> implements Transposable { +abstract class Scalable> + with ClassMixin + implements Transposable { /// Creates a new [Scalable]. const Scalable(); - /// The number of semitones that define this [Scalable]. - int get semitones; - /// Creates a new [PitchClass] from [semitones]. /// /// Example: @@ -22,6 +19,7 @@ abstract class Scalable> implements Transposable { /// Note.e.sharp.inOctave(2).toClass() == PitchClass.f /// Note.c.flat.flat.inOctave(5).toClass() == PitchClass.aSharp /// ``` + @override PitchClass toClass() => PitchClass(semitones); /// The [Interval] between this [Scalable] and [other]. @@ -53,22 +51,6 @@ extension ScalableIterable> on Iterable { } } - /// The [PitchClass] representation of this [ScalableIterable]. - Iterable toClass() => map((scalable) => scalable.toClass()); - - /// Whether this [Iterable] is enharmonically equivalent to [other]. - /// - /// Example: - /// ```dart - /// [Note.c.sharp, Note.f, Note.a.flat] - /// .isEnharmonicWith([Note.d.flat, Note.e.sharp, Note.g.sharp]) - /// == true - /// - /// [Note.d.sharp].isEnharmonicWith([Note.a.flat]) == false - /// ``` - bool isEnharmonicWith(Iterable other) => - const IterableEquality().equals(toClass(), other.toClass()); - /// Transposes this [Iterable] by [interval]. Iterable transposeBy(Interval interval) => map((item) => item.transposeBy(interval)); @@ -124,23 +106,3 @@ extension ScalableIterable> on Iterable { } } } - -/// An Interval iterable. -extension IntervalIterable on Iterable { - /// The [PitchClass] representation of this [IntervalIterable]. - Iterable toClass() => map((interval) => interval.toClass()); - - /// Whether this [Iterable] is enharmonically equivalent to [other]. - /// - /// Example: - /// ```dart - /// const [Interval.m2, Interval.m3, Interval.M2] - /// .isEnharmonicWith(const [Interval.m2, Interval.A2, Interval.d3]) - /// == true - /// - /// const [Interval.m2].isEnharmonicWith(const [Interval.P4]) == false - /// ``` - bool isEnharmonicWith(Iterable other) => - const IterableEquality() - .equals(toClass(), other.toClass()); -} diff --git a/lib/src/scale/scale.dart b/lib/src/scale/scale.dart index b9d3b891..647274ee 100644 --- a/lib/src/scale/scale.dart +++ b/lib/src/scale/scale.dart @@ -2,6 +2,7 @@ import 'package:collection/collection.dart' show ListEquality, UnmodifiableListView; import 'package:meta/meta.dart' show immutable; +import '../class_mixin.dart'; import '../harmony/chord.dart'; import '../harmony/harmonic_function.dart'; import '../interval/interval.dart'; @@ -147,6 +148,8 @@ class Scale> implements Transposable> { /// Whether this [Scale] is enharmonically equivalent to [other]. /// + /// See [Enharmonic equivalence](https://en.wikipedia.org/wiki/Enharmonic_equivalence). + /// /// Example: /// ```dart /// const Scale([Note.c, Note.d, Note.f, Note.g]) diff --git a/lib/src/scale/scale_pattern.dart b/lib/src/scale/scale_pattern.dart index d6c09768..185e5f7d 100644 --- a/lib/src/scale/scale_pattern.dart +++ b/lib/src/scale/scale_pattern.dart @@ -1,10 +1,9 @@ -import 'package:collection/collection.dart' - show IterableEquality, UnmodifiableListView; +import 'package:collection/collection.dart' show UnmodifiableListView; import 'package:meta/meta.dart' show immutable; +import '../class_mixin.dart'; import '../harmony/chord_pattern.dart'; import '../interval/interval.dart'; -import '../interval/interval_class.dart'; import '../scalable.dart'; import 'scale.dart'; import 'scale_degree.dart'; @@ -353,7 +352,9 @@ final class ScalePattern { Interval _addNextStepTo(int ordinal) => _stepFrom(ordinal) + _stepFrom(ordinal + 1); - /// Whether this [Scale] is enharmonically equivalent to [other]. + /// Whether this [ScalePattern] is enharmonically equivalent to [other]. + /// + /// See [Enharmonic equivalence](https://en.wikipedia.org/wiki/Enharmonic_equivalence). /// /// Example: /// ```dart