diff --git a/README.md b/README.md index 965976f9..97a3e0bf 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,8 @@ Pitch.parse('Eb3'); // E♭3 Create an `Interval`: ```dart -const Interval.perfect(5, PerfectQuality.perfect); // P5 Interval.P4; // P4 +const Interval.perfect(Size.twelfth, PerfectQuality.perfect); // P12 ``` Or turn it descending: diff --git a/example/main.dart b/example/main.dart index e9537326..81b7d860 100644 --- a/example/main.dart +++ b/example/main.dart @@ -21,8 +21,8 @@ void main() { Pitch.parse('Eb3'); // E♭3 // Intervals - const Interval.perfect(15, PerfectQuality.perfect); // P15 Interval.P4; // P4 + const Interval.perfect(Size.twelfth, PerfectQuality.perfect); // P12 -Interval.m6; // desc m6 Interval.M3.descending(); // desc M3 diff --git a/lib/music_notes.dart b/lib/music_notes.dart index c6b4e2db..03849086 100644 --- a/lib/music_notes.dart +++ b/lib/music_notes.dart @@ -9,6 +9,7 @@ export 'src/harmony/harmonic_function.dart'; export 'src/interval/interval.dart'; export 'src/interval/interval_class.dart'; export 'src/interval/quality.dart'; +export 'src/interval/size.dart'; export 'src/key/key.dart'; export 'src/key/key_signature.dart'; export 'src/key/mode.dart'; diff --git a/lib/src/chordable.dart b/lib/src/chordable.dart index 9c0d5ff7..d5d21da1 100644 --- a/lib/src/chordable.dart +++ b/lib/src/chordable.dart @@ -1,6 +1,7 @@ import 'harmony/chord.dart'; import 'interval/interval.dart'; import 'interval/quality.dart'; +import 'interval/size.dart'; /// A mixin for items that can form chords. /// @@ -28,23 +29,23 @@ mixin Chordable { /// Returns a new [T] adding a [quality] 6th. T add6([ImperfectQuality quality = ImperfectQuality.major]) => - add(Interval.imperfect(6, quality)); + add(Interval.imperfect(Size.sixth, quality)); /// Returns a new [T] adding a [quality] 7th. T add7([ImperfectQuality quality = ImperfectQuality.minor]) => - add(Interval.imperfect(7, quality)); + add(Interval.imperfect(Size.seventh, quality)); /// Returns a new [T] adding a [quality] 9th. T add9([ImperfectQuality quality = ImperfectQuality.major]) => - add(Interval.imperfect(9, quality)); + add(Interval.imperfect(Size.ninth, quality)); /// Returns a new [T] adding an [quality] 11th. T add11([PerfectQuality quality = PerfectQuality.perfect]) => - add(Interval.perfect(11, quality)); + add(Interval.perfect(Size.eleventh, quality)); /// Returns a new [T] adding a [quality] 13th. T add13([ImperfectQuality quality = ImperfectQuality.major]) => - add(Interval.imperfect(13, quality)); + add(Interval.imperfect(Size.thirteenth, quality)); /// Returns a new [T] adding [interval]. T add(Interval interval, {Set? replaceSizes}); diff --git a/lib/src/interval/interval.dart b/lib/src/interval/interval.dart index 6d919a4d..26c9b479 100644 --- a/lib/src/interval/interval.dart +++ b/lib/src/interval/interval.dart @@ -1,14 +1,13 @@ // ignore_for_file: constant_identifier_names -import 'package:collection/collection.dart' show IterableExtension; import 'package:meta/meta.dart' show immutable; import 'package:music_notes/utils.dart'; -import '../music.dart'; import '../note/note.dart'; import '../scalable.dart'; import 'interval_class.dart'; import 'quality.dart'; +import 'size.dart'; /// Distance between two notes. /// @@ -20,7 +19,7 @@ import 'quality.dart'; final class Interval implements Comparable { /// Number of lines and spaces (or alphabet letters) spanning the two notes, /// including the beginning and end. - final int size; + final Size size; /// The quality of this [Interval]. /// @@ -28,172 +27,167 @@ final class Interval implements Comparable { /// depending on the nature of this [Interval]. final Quality quality; - const Interval._(this.size, this.quality) - : assert(size != 0, 'Size must be non-zero'); + const Interval._(this.size, this.quality); /// A diminished unison [Interval]. - static const d1 = Interval.perfect(1, PerfectQuality.diminished); + static const d1 = Interval.perfect(Size.unison, PerfectQuality.diminished); /// A perfect unison [Interval]. - static const P1 = Interval.perfect(1, PerfectQuality.perfect); + static const P1 = Interval.perfect(Size.unison, PerfectQuality.perfect); /// An augmented unison [Interval]. - static const A1 = Interval.perfect(1, PerfectQuality.augmented); + static const A1 = Interval.perfect(Size.unison, PerfectQuality.augmented); /// A diminished second [Interval]. - static const d2 = Interval.imperfect(2, ImperfectQuality.diminished); + static const d2 = + Interval.imperfect(Size.second, ImperfectQuality.diminished); /// A minor second [Interval]. - static const m2 = Interval.imperfect(2, ImperfectQuality.minor); + static const m2 = Interval.imperfect(Size.second, ImperfectQuality.minor); /// A major second [Interval]. - static const M2 = Interval.imperfect(2, ImperfectQuality.major); + static const M2 = Interval.imperfect(Size.second, ImperfectQuality.major); /// An augmented second [Interval]. - static const A2 = Interval.imperfect(2, ImperfectQuality.augmented); + static const A2 = Interval.imperfect(Size.second, ImperfectQuality.augmented); /// A diminished third [Interval]. - static const d3 = Interval.imperfect(3, ImperfectQuality.diminished); + static const d3 = Interval.imperfect(Size.third, ImperfectQuality.diminished); /// A minor third [Interval]. - static const m3 = Interval.imperfect(3, ImperfectQuality.minor); + static const m3 = Interval.imperfect(Size.third, ImperfectQuality.minor); /// A major third [Interval]. - static const M3 = Interval.imperfect(3, ImperfectQuality.major); + static const M3 = Interval.imperfect(Size.third, ImperfectQuality.major); /// An augmented third [Interval]. - static const A3 = Interval.imperfect(3, ImperfectQuality.augmented); + static const A3 = Interval.imperfect(Size.third, ImperfectQuality.augmented); /// A diminished fourth [Interval]. - static const d4 = Interval.perfect(4, PerfectQuality.diminished); + static const d4 = Interval.perfect(Size.fourth, PerfectQuality.diminished); /// A perfect fourth [Interval]. - static const P4 = Interval.perfect(4, PerfectQuality.perfect); + static const P4 = Interval.perfect(Size.fourth, PerfectQuality.perfect); /// An augmented fourth [Interval]. - static const A4 = Interval.perfect(4, PerfectQuality.augmented); + static const A4 = Interval.perfect(Size.fourth, PerfectQuality.augmented); /// A diminished fifth [Interval]. - static const d5 = Interval.perfect(5, PerfectQuality.diminished); + static const d5 = Interval.perfect(Size.fifth, PerfectQuality.diminished); /// A perfect fifth [Interval]. - static const P5 = Interval.perfect(5, PerfectQuality.perfect); + static const P5 = Interval.perfect(Size.fifth, PerfectQuality.perfect); /// An augmented fifth [Interval]. - static const A5 = Interval.perfect(5, PerfectQuality.augmented); + static const A5 = Interval.perfect(Size.fifth, PerfectQuality.augmented); /// A diminished sixth [Interval]. - static const d6 = Interval.imperfect(6, ImperfectQuality.diminished); + static const d6 = Interval.imperfect(Size.sixth, ImperfectQuality.diminished); /// A minor sixth [Interval]. - static const m6 = Interval.imperfect(6, ImperfectQuality.minor); + static const m6 = Interval.imperfect(Size.sixth, ImperfectQuality.minor); /// A major sixth [Interval]. - static const M6 = Interval.imperfect(6, ImperfectQuality.major); + static const M6 = Interval.imperfect(Size.sixth, ImperfectQuality.major); /// An augmented sixth [Interval]. - static const A6 = Interval.imperfect(6, ImperfectQuality.augmented); + static const A6 = Interval.imperfect(Size.sixth, ImperfectQuality.augmented); /// A diminished seventh [Interval]. - static const d7 = Interval.imperfect(7, ImperfectQuality.diminished); + static const d7 = + Interval.imperfect(Size.seventh, ImperfectQuality.diminished); /// A minor seventh [Interval]. - static const m7 = Interval.imperfect(7, ImperfectQuality.minor); + static const m7 = Interval.imperfect(Size.seventh, ImperfectQuality.minor); /// A major seventh [Interval]. - static const M7 = Interval.imperfect(7, ImperfectQuality.major); + static const M7 = Interval.imperfect(Size.seventh, ImperfectQuality.major); /// An augmented seventh [Interval]. - static const A7 = Interval.imperfect(7, ImperfectQuality.augmented); + static const A7 = + Interval.imperfect(Size.seventh, ImperfectQuality.augmented); /// A diminished octave [Interval]. - static const d8 = Interval.perfect(8, PerfectQuality.diminished); + static const d8 = Interval.perfect(Size.octave, PerfectQuality.diminished); /// A perfect octave [Interval]. - static const P8 = Interval.perfect(8, PerfectQuality.perfect); + static const P8 = Interval.perfect(Size.octave, PerfectQuality.perfect); /// An augmented octave [Interval]. - static const A8 = Interval.perfect(8, PerfectQuality.augmented); + static const A8 = Interval.perfect(Size.octave, PerfectQuality.augmented); /// A diminished ninth [Interval]. - static const d9 = Interval.imperfect(9, ImperfectQuality.diminished); + static const d9 = Interval.imperfect(Size.ninth, ImperfectQuality.diminished); /// A minor ninth [Interval]. - static const m9 = Interval.imperfect(9, ImperfectQuality.minor); + static const m9 = Interval.imperfect(Size.ninth, ImperfectQuality.minor); /// A major ninth [Interval]. - static const M9 = Interval.imperfect(9, ImperfectQuality.major); + static const M9 = Interval.imperfect(Size.ninth, ImperfectQuality.major); /// An augmented ninth [Interval]. - static const A9 = Interval.imperfect(9, ImperfectQuality.augmented); + static const A9 = Interval.imperfect(Size.ninth, ImperfectQuality.augmented); /// A diminished eleventh [Interval]. - static const d11 = Interval.perfect(11, PerfectQuality.diminished); + static const d11 = Interval.perfect(Size.eleventh, PerfectQuality.diminished); /// A perfect eleventh [Interval]. - static const P11 = Interval.perfect(11, PerfectQuality.perfect); + static const P11 = Interval.perfect(Size.eleventh, PerfectQuality.perfect); /// An augmented eleventh [Interval]. - static const A11 = Interval.perfect(11, PerfectQuality.augmented); + static const A11 = Interval.perfect(Size.eleventh, PerfectQuality.augmented); /// A diminished thirteenth [Interval]. - static const d13 = Interval.imperfect(13, ImperfectQuality.diminished); + static const d13 = + Interval.imperfect(Size.thirteenth, ImperfectQuality.diminished); /// A minor thirteenth [Interval]. - static const m13 = Interval.imperfect(13, ImperfectQuality.minor); + static const m13 = + Interval.imperfect(Size.thirteenth, ImperfectQuality.minor); /// A major thirteenth [Interval]. - static const M13 = Interval.imperfect(13, ImperfectQuality.major); + static const M13 = + Interval.imperfect(Size.thirteenth, ImperfectQuality.major); /// An augmented thirteenth [Interval]. - static const A13 = Interval.imperfect(13, ImperfectQuality.augmented); - - /// [Interval.size] to the corresponding [ImperfectQuality.minor] or - /// [PerfectQuality.perfect] semitones. - static const _sizeToSemitones = { - 1: 0, // P - 2: 1, // m - 3: 3, // m - 4: 5, // P - 5: 7, // P - 6: 8, // m - 7: 10, // m - 8: 12, // P - }; + static const A13 = + Interval.imperfect(Size.thirteenth, ImperfectQuality.augmented); static final _regExp = RegExp(r'(\w+?)(\d+)'); /// Creates a new [Interval] allowing only perfect quality [size]s. const Interval.perfect(this.size, PerfectQuality this.quality) - : assert(size != 0, 'Size must be non-zero'), - // Copied from [_IntervalSize._isPerfect] to allow const. - assert( - ((size < 0 ? -size : size) + (size < 0 ? -size : size) ~/ 8) % 4 < 2, + // Copied from [Size.isPerfect] to allow const. + : assert( + ((size < 0 ? 0 - size : size) + (size < 0 ? 0 - size : size) ~/ 8) % + 4 < + 2, 'Interval must be perfect', ); /// Creates a new [Interval] allowing only imperfect quality [size]s. const Interval.imperfect(this.size, ImperfectQuality this.quality) - : assert(size != 0, 'Size must be non-zero'), - // Copied from [_IntervalSize._isPerfect] to allow const. - assert( - ((size < 0 ? -size : size) + (size < 0 ? -size : size) ~/ 8) % 4 >= 2, + // Copied from [Size.isPerfect] to allow const. + : assert( + ((size < 0 ? 0 - size : size) + (size < 0 ? 0 - size : size) ~/ 8) % + 4 >= + 2, 'Interval must be imperfect', ); /// Creates a new [Interval] from [size] and [Quality.semitones]. - factory Interval.fromQualitySemitones(int size, int semitones) { + factory Interval.fromQualitySemitones(Size size, int semitones) { final qualityConstructor = - size._isPerfect ? PerfectQuality.new : ImperfectQuality.new; + size.isPerfect ? PerfectQuality.new : ImperfectQuality.new; return Interval._(size, qualityConstructor(semitones)); } /// Creates a new [Interval] from [size] and [Interval.semitones]. - factory Interval.fromSemitones(int size, int semitones) => + factory Interval.fromSemitones(Size size, int semitones) => Interval.fromQualitySemitones( size, - semitones * size.sign - size._semitones.abs(), + semitones * size.sign - size.semitones.abs(), ); /// Parse [source] as an [Interval] and return its value. @@ -211,43 +205,13 @@ final class Interval implements Comparable { final match = _regExp.firstMatch(source); if (match == null) throw FormatException('Invalid Interval', source); - final size = int.parse(match[2]!); + final size = Size(int.parse(match[2]!)); final parseFactory = - size._isPerfect ? PerfectQuality.parse : ImperfectQuality.parse; + size.isPerfect ? PerfectQuality.parse : ImperfectQuality.parse; return Interval._(size, parseFactory(match[1]!)); } - /// Returns the [Interval.size] that matches with [semitones] - /// in [_sizeToSemitones], otherwise returns `null`. - /// - /// Example: - /// ```dart - /// Interval.sizeFromSemitones(8) == 6 - /// Interval.sizeFromSemitones(0) == 1 - /// Interval.sizeFromSemitones(-12) == -8 - /// Interval.sizeFromSemitones(4) == null - /// ``` - static int? sizeFromSemitones(int semitones) { - final absoluteSemitones = semitones.abs(); - final matchingSize = _sizeToSemitones.keys.firstWhereOrNull( - (size) => - (absoluteSemitones == chromaticDivisions - ? chromaticDivisions - : absoluteSemitones % chromaticDivisions) == - _sizeToSemitones[size], - ); - if (matchingSize == null) return null; - if (absoluteSemitones == chromaticDivisions) { - return matchingSize * semitones.sign; - } - - final absResult = - matchingSize + (absoluteSemitones ~/ chromaticDivisions) * 7; - - return absResult * semitones.nonZeroSign; - } - /// Returns the number of semitones of this [Interval]. /// /// Example: @@ -257,7 +221,7 @@ final class Interval implements Comparable { /// Interval.A4.semitones == 6 /// (-Interval.M3).semitones == -4 /// ``` - int get semitones => (size._semitones.abs() + quality.semitones) * size.sign; + int get semitones => (size.semitones.abs() + quality.semitones) * size.sign; /// Whether this [Interval] is descending. /// @@ -278,8 +242,10 @@ final class Interval implements Comparable { /// (-Interval.P5).descending() == -Interval.P5 /// (-Interval.M7).descending(isDescending: false) == Interval.M7 /// ``` - Interval descending({bool isDescending = true}) => - Interval._(size * (this.isDescending == isDescending ? 1 : -1), quality); + Interval descending({bool isDescending = true}) => Interval._( + Size(size * (this.isDescending == isDescending ? 1 : -1)), + quality, + ); /// Returns the inverted of this [Interval]. /// @@ -291,8 +257,8 @@ final class Interval implements Comparable { /// (-Interval.P1).inverted == -Interval.P8 /// ``` /// - /// If this [Interval] is greater than an octave, the simplified inversion is - /// returned instead. + /// If this [Interval] is greater than [Size.octave], the simplified inversion + /// is returned instead. /// /// Example: /// ```dart @@ -301,7 +267,8 @@ final class Interval implements Comparable { /// ``` Interval get inverted { final diff = 9 - simplified.size.abs(); - final invertedSize = (diff.isNegative ? diff.abs() + 2 : diff) * size.sign; + final invertedSize = + Size((diff.isNegative ? diff.abs() + 2 : diff) * size.sign); return Interval._(invertedSize, quality.inverted); } @@ -315,9 +282,9 @@ final class Interval implements Comparable { /// Interval.P8.simplified == Interval.P8 /// (-Interval.M3).simplified == -Interval.M3 /// ``` - Interval get simplified => Interval._(size._simplified, quality); + Interval get simplified => Interval._(size.simplified, quality); - /// Returns whether this [Interval] is greater than an octave. + /// Returns whether this [Interval] is greater than [Size.octave]. /// /// Example: /// ```dart @@ -328,7 +295,7 @@ final class Interval implements Comparable { /// (-Interval.P11).isCompound == true /// Interval.m13.isCompound == true /// ``` - bool get isCompound => size._isCompound; + bool get isCompound => size.isCompound; /// Whether this [Interval] is dissonant. /// @@ -353,10 +320,10 @@ final class Interval implements Comparable { /// /// Example: /// ```dart - /// Interval.A4.respellBySize(5) == Interval.d5 - /// Interval.d3.respellBySize(2) == Interval.M2 + /// Interval.A4.respellBySize(Size.fifth) == Interval.d5 + /// Interval.d3.respellBySize(Size.second) == Interval.M2 /// ``` - Interval respellBySize(int size) => Interval.fromSemitones(size, semitones); + Interval respellBySize(Size size) => Interval.fromSemitones(size, semitones); /// Returns the iteration distance of this [Interval] between [scalable1] and /// [scalable2], including all visited `notes`. @@ -475,73 +442,3 @@ final class Interval implements Comparable { () => semitones.compareTo(other.semitones), ]); } - -/// An [Interval.size] int extension. -extension IntervalSize on int { - /// Returns the number of semitones of this [Interval.size] for the - /// corresponding [ImperfectQuality.minor] or [PerfectQuality.perfect] - /// semitones. - /// - /// See [Interval._sizeToSemitones]. - /// - /// Example: - /// ```dart - /// 3._semitones == 3 - /// 5._semitones == 7 - /// (-5)._semitones == -7 - /// 7._semitones == 10 - /// 9._semitones == 13 - /// (-9)._semitones == -13 - /// ``` - int get _semitones { - final simplifiedAbs = _simplified.abs(); - final octaveShift = chromaticDivisions * (sizeAbsShift ~/ 8); - // We exclude perfect octaves (simplified as 8) from the lookup to consider - // them 0 (as if they were modulo 8). - final size = simplifiedAbs == 8 ? 1 : simplifiedAbs; - - return (Interval._sizeToSemitones[size]! + octaveShift) * sign; - } - - /// Returns the absolute [Interval.size] value taking octave shift into - /// account. - int get sizeAbsShift { - final sizeAbs = abs(); - - return sizeAbs + sizeAbs ~/ 8; - } - - /// Returns whether this [Interval.size] conforms a perfect interval. - /// - /// Example: - /// ```dart - /// 5._isPerfect == true - /// 6._isPerfect == false - /// (-11)._isPerfect == true - /// ``` - bool get _isPerfect => sizeAbsShift % 4 < 2; - - /// Returns whether this [Interval.size] is greater than an octave. - /// - /// Example: - /// ```dart - /// 5._isCompound == false - /// (-6)._isCompound == false - /// 8._isCompound == false - /// 9._isCompound == true - /// (-11)._isCompound == true - /// 13._isCompound == true - /// ``` - bool get _isCompound => abs() > 8; - - /// Returns the simplified version of this [Interval.size]. - /// - /// Example: - /// ```dart - /// 13._simplified == 6 - /// (-9)._simplified == -2 - /// 8._simplified == 8 - /// (-22)._simplified == -8 - /// ``` - int get _simplified => _isCompound ? sizeAbsShift.nonZeroMod(8) * sign : this; -} diff --git a/lib/src/interval/interval_class.dart b/lib/src/interval/interval_class.dart index b6892d35..2bf7eae6 100644 --- a/lib/src/interval/interval_class.dart +++ b/lib/src/interval/interval_class.dart @@ -10,6 +10,7 @@ import '../music.dart'; import '../note/pitch_class.dart'; import 'interval.dart'; import 'quality.dart'; +import 'size.dart'; /// The shortest distance in pitch class space between two unordered /// [PitchClass]es. @@ -76,15 +77,15 @@ final class IntervalClass implements Comparable { /// ``` Set spellings({int distance = 0}) { assert(distance >= 0, 'Distance must be greater or equal than zero.'); - final size = Interval.sizeFromSemitones(semitones); + final size = Size.fromSemitones(semitones); if (size != null) { return SplayTreeSet.of({ Interval.fromSemitones(size, semitones), for (var i = 1; i <= distance; i++) ...[ if (size.incrementBy(-i) != 0) - Interval.fromSemitones(size.incrementBy(-i), semitones), - Interval.fromSemitones(size.incrementBy(i), semitones), + Interval.fromSemitones(Size(size.incrementBy(-i)), semitones), + Interval.fromSemitones(Size(size.incrementBy(i)), semitones), ], }); } @@ -94,11 +95,11 @@ final class IntervalClass implements Comparable { return SplayTreeSet.of({ for (var i = 1; i <= distanceClamp; i++) ...[ Interval.fromSemitones( - Interval.sizeFromSemitones(semitones.incrementBy(-i))!, + Size.fromSemitones(semitones.incrementBy(-i))!, semitones, ), Interval.fromSemitones( - Interval.sizeFromSemitones(semitones.incrementBy(i))!, + Size.fromSemitones(semitones.incrementBy(i))!, semitones, ), ], diff --git a/lib/src/interval/size.dart b/lib/src/interval/size.dart new file mode 100644 index 00000000..a758001c --- /dev/null +++ b/lib/src/interval/size.dart @@ -0,0 +1,170 @@ +import 'package:collection/collection.dart' show IterableExtension; +import 'package:meta/meta.dart' show immutable, redeclare; +import 'package:music_notes/utils.dart'; + +import '../music.dart'; +import 'interval.dart'; +import 'quality.dart'; + +/// An [Interval] size. +@immutable +extension type const Size._(int value) implements int { + /// Creates a new [Size] from [value]. + const Size(this.value) : assert(value != 0, 'Value must be non-zero'); + + /// A unison [Size]. + static const unison = Size(1); + + /// A second [Size]. + static const second = Size(2); + + /// A third [Size]. + static const third = Size(3); + + /// A fourth [Size]. + static const fourth = Size(4); + + /// A fifth [Size]. + static const fifth = Size(5); + + /// A sixth [Size]. + static const sixth = Size(6); + + /// A seventh [Size]. + static const seventh = Size(7); + + /// An octave [Size]. + static const octave = Size(8); + + /// A ninth [Size]. + static const ninth = Size(9); + + /// A tenth [Size]. + static const tenth = Size(10); + + /// An eleventh [Size]. + static const eleventh = Size(11); + + /// A twelfth [Size]. + static const twelfth = Size(12); + + /// A thirteenth [Size]. + static const thirteenth = Size(13); + + /// [Size] to the corresponding [ImperfectQuality.minor] or + /// [PerfectQuality.perfect] semitones. + static const _sizeToSemitones = { + Size.unison: 0, // P + Size.second: 1, // m + Size.third: 3, // m + Size.fourth: 5, // P + Size.fifth: 7, // P + Size.sixth: 8, // m + Size.seventh: 10, // m + Size.octave: 12, // P + }; + + /// Returns the [Size] that matches with [semitones] in [_sizeToSemitones], + /// otherwise returns `null`. + /// + /// Example: + /// ```dart + /// Size.fromSemitones(8) == Size.sixth + /// Size.fromSemitones(0) == Size.unison + /// Size.fromSemitones(-12) == -Size.octave + /// Size.fromSemitones(4) == null + /// ``` + static Size? fromSemitones(int semitones) { + final absoluteSemitones = semitones.abs(); + final matchingSize = _sizeToSemitones.keys.firstWhereOrNull( + (size) => + (absoluteSemitones == chromaticDivisions + ? chromaticDivisions + : absoluteSemitones % chromaticDivisions) == + _sizeToSemitones[size], + ); + if (matchingSize == null) return null; + if (absoluteSemitones == chromaticDivisions) { + return Size(matchingSize * semitones.sign); + } + + final absResult = + matchingSize + (absoluteSemitones ~/ chromaticDivisions) * 7; + + return Size(absResult * semitones.nonZeroSign); + } + + /// Returns the number of semitones of this [Size] as in [_sizeToSemitones]. + /// + /// Example: + /// ```dart + /// Size.third.semitones == 3 + /// Size.fifth.semitones == 7 + /// (-Size.fifth).semitones == -7 + /// Size.seventh.semitones == 10 + /// Size.ninth.semitones == 13 + /// (-Size.ninth).semitones == -13 + /// ``` + int get semitones { + final simplifiedAbs = simplified.abs(); + final octaveShift = chromaticDivisions * (absShift ~/ Size.octave); + // We exclude perfect octaves (simplified as 8) from the lookup to consider + // them 0 (as if they were modulo `Size.octave`). + final size = Size(simplifiedAbs == Size.octave ? 1 : simplifiedAbs); + + return (_sizeToSemitones[size]! + octaveShift) * sign; + } + + /// Returns the absolute [Size] value taking octave shift into account. + int get absShift { + final sizeAbs = abs(); + + return sizeAbs + sizeAbs ~/ Size.octave; + } + + /// Returns whether this [Size] conforms a perfect interval. + /// + /// Example: + /// ```dart + /// Size.fifth.isPerfect == true + /// Size.sixth.isPerfect == false + /// (-Size.eleventh).isPerfect == true + /// ``` + bool get isPerfect => absShift % (Size.octave / 2) < 2; + + /// Returns whether this [Size] is greater than [Size.octave] + /// + /// Example: + /// ```dart + /// Size.fifth.isCompound == false + /// (-Size.sixth).isCompound == false + /// Size.octave.isCompound == false + /// Size.ninth.isCompound == true + /// (-Size.eleventh).isCompound == true + /// Size.thirteenth.isCompound == true + /// ``` + bool get isCompound => abs() > Size.octave; + + /// Returns the simplified version of this [Size]. + /// + /// Example: + /// ```dart + /// Size.thirteenth.simplified == Size.sixth + /// (-Size.ninth).simplified == -Size.second + /// Size.octave.simplified == Size.octave + /// const Size(-22).simplified == -Size.octave + /// ``` + Size get simplified => Size( + isCompound ? absShift.nonZeroMod(Size.octave) * sign : value, + ); + + /// The negation of this [Size]. + /// + /// Example: + /// ```dart + /// -Size.fifth == const Size(-5) + /// -const Size(-7) == Size.seventh + /// ``` + @redeclare + Size operator -() => Size(-value); +} diff --git a/lib/src/note/base_note.dart b/lib/src/note/base_note.dart index 02e425dc..8b73c1ed 100644 --- a/lib/src/note/base_note.dart +++ b/lib/src/note/base_note.dart @@ -1,7 +1,7 @@ import 'package:collection/collection.dart' show IterableExtension; import 'package:music_notes/utils.dart'; -import '../interval/interval.dart'; +import '../interval/size.dart'; import '../music.dart'; import 'note.dart'; @@ -94,19 +94,19 @@ enum BaseNote implements Comparable { /// ``` int get ordinal => values.indexOf(this) + 1; - /// Returns the [Interval.size] that conforms between this [BaseNote] and - /// [other]. + /// Returns the [Size] that conforms between this [BaseNote] and [other]. /// /// Example: /// ```dart - /// BaseNote.d.intervalSize(BaseNote.f) == 3 - /// BaseNote.a.intervalSize(BaseNote.e) == 5 + /// BaseNote.d.intervalSize(BaseNote.f) == Size.third + /// BaseNote.a.intervalSize(BaseNote.e) == Size.fifth /// ``` - int intervalSize(BaseNote other) => - other.ordinal - - ordinal + - (ordinal > other.ordinal ? values.length : 0) + - 1; + Size intervalSize(BaseNote other) => Size( + other.ordinal - + ordinal + + (ordinal > other.ordinal ? values.length : 0) + + 1, + ); /// Returns the difference in semitones between this [BaseNote] and [other]. /// @@ -140,15 +140,12 @@ enum BaseNote implements Comparable { /// /// Example: /// ```dart - /// BaseNote.g.transposeBySize(1) == BaseNote.g - /// BaseNote.g.transposeBySize(5) == BaseNote.d + /// BaseNote.g.transposeBySize(Size.unison) == BaseNote.g + /// BaseNote.g.transposeBySize(Size.fifth) == BaseNote.d /// BaseNote.a.transposeBySize(-3) == BaseNote.f /// ``` - BaseNote transposeBySize(int size) { - assert(size != 0, 'Size must be non-zero'); - - return BaseNote.fromOrdinal(ordinal + size.incrementBy(-1)); - } + BaseNote transposeBySize(Size size) => + BaseNote.fromOrdinal(ordinal + size.incrementBy(-1)); @override String toString({NoteNotation system = NoteNotation.english}) => diff --git a/lib/src/note/note.dart b/lib/src/note/note.dart index 59e7204c..1928adb9 100644 --- a/lib/src/note/note.dart +++ b/lib/src/note/note.dart @@ -4,6 +4,7 @@ import 'package:music_notes/utils.dart'; import '../harmony/chord.dart'; import '../harmony/chord_pattern.dart'; import '../interval/interval.dart'; +import '../interval/size.dart'; import '../key/key.dart'; import '../key/key_signature.dart'; import '../key/mode.dart'; @@ -404,7 +405,7 @@ final class Note extends Scalable implements Comparable { final accidentalSemitones = (accidental.semitones * interval.size.sign) + ((interval.semitones * interval.size.sign) - positiveDifference); final semitonesOctaveMod = accidentalSemitones - - chromaticDivisions * (interval.size.sizeAbsShift ~/ 8); + chromaticDivisions * (interval.size.absShift ~/ Size.octave); return Note( transposedBaseNote, diff --git a/lib/src/note/pitch.dart b/lib/src/note/pitch.dart index e6448873..8fcaeab4 100644 --- a/lib/src/note/pitch.dart +++ b/lib/src/note/pitch.dart @@ -4,6 +4,7 @@ import 'package:music_notes/utils.dart'; import '../harmony/chord.dart'; import '../harmony/chord_pattern.dart'; import '../interval/interval.dart'; +import '../interval/size.dart'; import '../music.dart'; import '../scalable.dart'; import '../tuning/cent.dart'; @@ -370,7 +371,7 @@ final class Pitch extends Scalable implements Comparable { (7 + (intervalSize.isNegative ? 2 : 0)) * (other.octave - octave); return Interval.fromSemitones( - intervalSize + octaveShift, + Size(intervalSize + octaveShift), difference(other), ); } diff --git a/lib/src/scale/scale.dart b/lib/src/scale/scale.dart index b551111c..6d9aec84 100644 --- a/lib/src/scale/scale.dart +++ b/lib/src/scale/scale.dart @@ -5,6 +5,7 @@ import '../harmony/chord.dart'; 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'; @@ -99,7 +100,7 @@ class Scale> implements Transposable> { return scalable.transposeBy( Interval.perfect( - 1, + Size.unison, PerfectQuality(scaleDegree.semitonesDelta.abs()), ).descending(isDescending: scaleDegree.semitonesDelta.isNegative), ); diff --git a/lib/src/tuning/just_intonation.dart b/lib/src/tuning/just_intonation.dart index cb047c6b..c977c6a3 100644 --- a/lib/src/tuning/just_intonation.dart +++ b/lib/src/tuning/just_intonation.dart @@ -54,8 +54,8 @@ class PythagoreanTuning extends JustIntonation { ratio *= distance.isNegative ? JustIntonation.ascendingFourthRatio : JustIntonation.ascendingFifthRatio; - // When ratio is larger than 2, so larger than an octave, divide by 2 to - // transpose it down by one octave. + // When ratio is greater than 2, so greater than [Size.octave], + // divide by 2 to transpose it down by one octave. if (ratio >= 2) ratio /= 2; } diff --git a/test/src/interval/interval_class_test.dart b/test/src/interval/interval_class_test.dart index a88946da..21873b94 100644 --- a/test/src/interval/interval_class_test.dart +++ b/test/src/interval/interval_class_test.dart @@ -26,7 +26,10 @@ void main() { expect(IntervalClass.m2.spellings(distance: 1), { Interval.A1, Interval.m2, - const Interval.imperfect(3, ImperfectQuality.doublyDiminished), + const Interval.imperfect( + Size.third, + ImperfectQuality.doublyDiminished, + ), }); expect(IntervalClass.M2.spellings(), {Interval.M2, Interval.d3}); @@ -39,7 +42,10 @@ void main() { expect(IntervalClass.m3.spellings(distance: 1), { Interval.A2, Interval.m3, - const Interval.perfect(4, PerfectQuality.doublyDiminished), + const Interval.perfect( + Size.fourth, + PerfectQuality.doublyDiminished, + ), }); expect(IntervalClass.M3.spellings(), {Interval.M3, Interval.d4}); @@ -52,7 +58,7 @@ void main() { expect(IntervalClass.P4.spellings(distance: 1), { Interval.A3, Interval.P4, - const Interval.perfect(5, PerfectQuality.doublyDiminished), + const Interval.perfect(Size.fifth, PerfectQuality.doublyDiminished), }); expect( diff --git a/test/src/interval/interval_test.dart b/test/src/interval/interval_test.dart index 6b7d036b..b04cf479 100644 --- a/test/src/interval/interval_test.dart +++ b/test/src/interval/interval_test.dart @@ -8,19 +8,11 @@ void main() { group('constructor', () { test('throws an assertion error when arguments are incorrect', () { expect( - () => Interval.perfect(0, PerfectQuality.perfect), + () => Interval.perfect(Size.second, PerfectQuality.diminished), throwsA(isA()), ); expect( - () => Interval.imperfect(0, ImperfectQuality.minor), - throwsA(isA()), - ); - expect( - () => Interval.perfect(2, PerfectQuality.diminished), - throwsA(isA()), - ); - expect( - () => Interval.imperfect(5, ImperfectQuality.augmented), + () => Interval.imperfect(Size.fifth, ImperfectQuality.augmented), throwsA(isA()), ); }); @@ -28,61 +20,61 @@ void main() { group('.fromSemitones()', () { test('creates a new Interval from semitones', () { - expect(Interval.fromSemitones(1, -1), Interval.d1); - expect(Interval.fromSemitones(-1, 1), -Interval.d1); - expect(Interval.fromSemitones(1, 0), Interval.P1); - expect(Interval.fromSemitones(-1, 0), -Interval.P1); - expect(Interval.fromSemitones(1, 1), Interval.A1); - expect(Interval.fromSemitones(-1, -1), -Interval.A1); - - expect(Interval.fromSemitones(2, 0), Interval.d2); - expect(Interval.fromSemitones(-2, 0), -Interval.d2); - expect(Interval.fromSemitones(2, 1), Interval.m2); - expect(Interval.fromSemitones(-2, -1), -Interval.m2); - expect(Interval.fromSemitones(2, 2), Interval.M2); - expect(Interval.fromSemitones(-2, -2), -Interval.M2); - expect(Interval.fromSemitones(2, 3), Interval.A2); - expect(Interval.fromSemitones(-2, -3), -Interval.A2); - - expect(Interval.fromSemitones(3, 2), Interval.d3); - expect(Interval.fromSemitones(-3, -2), -Interval.d3); - expect(Interval.fromSemitones(3, 3), Interval.m3); - expect(Interval.fromSemitones(-3, -3), -Interval.m3); - expect(Interval.fromSemitones(3, 4), Interval.M3); - expect(Interval.fromSemitones(-3, -4), -Interval.M3); - expect(Interval.fromSemitones(3, 5), Interval.A3); - expect(Interval.fromSemitones(-3, -5), -Interval.A3); - - expect(Interval.fromSemitones(4, 4), Interval.d4); - expect(Interval.fromSemitones(-4, -4), -Interval.d4); - expect(Interval.fromSemitones(4, 5), Interval.P4); - expect(Interval.fromSemitones(-4, -5), -Interval.P4); - expect(Interval.fromSemitones(4, 6), Interval.A4); - expect(Interval.fromSemitones(-4, -6), -Interval.A4); - - expect(Interval.fromSemitones(5, 6), Interval.d5); - expect(Interval.fromSemitones(-5, -6), -Interval.d5); - expect(Interval.fromSemitones(5, 7), Interval.P5); - expect(Interval.fromSemitones(-5, -7), -Interval.P5); - expect(Interval.fromSemitones(5, 8), Interval.A5); - expect(Interval.fromSemitones(-5, -8), -Interval.A5); - - expect(Interval.fromSemitones(6, 8), Interval.m6); - expect(Interval.fromSemitones(-6, -8), -Interval.m6); - expect(Interval.fromSemitones(6, 9), Interval.M6); - expect(Interval.fromSemitones(-6, -9), -Interval.M6); - - expect(Interval.fromSemitones(7, 10), Interval.m7); - expect(Interval.fromSemitones(-7, -10), -Interval.m7); - expect(Interval.fromSemitones(7, 11), Interval.M7); - expect(Interval.fromSemitones(-7, -11), -Interval.M7); - - expect(Interval.fromSemitones(8, 11), Interval.d8); - expect(Interval.fromSemitones(-8, -11), -Interval.d8); - expect(Interval.fromSemitones(8, 12), Interval.P8); - expect(Interval.fromSemitones(-8, -12), -Interval.P8); - expect(Interval.fromSemitones(8, 13), Interval.A8); - expect(Interval.fromSemitones(-8, -13), -Interval.A8); + expect(Interval.fromSemitones(Size.unison, -1), Interval.d1); + expect(Interval.fromSemitones(-Size.unison, 1), -Interval.d1); + expect(Interval.fromSemitones(Size.unison, 0), Interval.P1); + expect(Interval.fromSemitones(-Size.unison, 0), -Interval.P1); + expect(Interval.fromSemitones(Size.unison, 1), Interval.A1); + expect(Interval.fromSemitones(-Size.unison, -1), -Interval.A1); + + expect(Interval.fromSemitones(Size.second, 0), Interval.d2); + expect(Interval.fromSemitones(-Size.second, 0), -Interval.d2); + expect(Interval.fromSemitones(Size.second, 1), Interval.m2); + expect(Interval.fromSemitones(-Size.second, -1), -Interval.m2); + expect(Interval.fromSemitones(Size.second, 2), Interval.M2); + expect(Interval.fromSemitones(-Size.second, -2), -Interval.M2); + expect(Interval.fromSemitones(Size.second, 3), Interval.A2); + expect(Interval.fromSemitones(-Size.second, -3), -Interval.A2); + + expect(Interval.fromSemitones(Size.third, 2), Interval.d3); + expect(Interval.fromSemitones(-Size.third, -2), -Interval.d3); + expect(Interval.fromSemitones(Size.third, 3), Interval.m3); + expect(Interval.fromSemitones(-Size.third, -3), -Interval.m3); + expect(Interval.fromSemitones(Size.third, 4), Interval.M3); + expect(Interval.fromSemitones(-Size.third, -4), -Interval.M3); + expect(Interval.fromSemitones(Size.third, 5), Interval.A3); + expect(Interval.fromSemitones(-Size.third, -5), -Interval.A3); + + expect(Interval.fromSemitones(Size.fourth, 4), Interval.d4); + expect(Interval.fromSemitones(-Size.fourth, -4), -Interval.d4); + expect(Interval.fromSemitones(Size.fourth, 5), Interval.P4); + expect(Interval.fromSemitones(-Size.fourth, -5), -Interval.P4); + expect(Interval.fromSemitones(Size.fourth, 6), Interval.A4); + expect(Interval.fromSemitones(-Size.fourth, -6), -Interval.A4); + + expect(Interval.fromSemitones(Size.fifth, 6), Interval.d5); + expect(Interval.fromSemitones(-Size.fifth, -6), -Interval.d5); + expect(Interval.fromSemitones(Size.fifth, 7), Interval.P5); + expect(Interval.fromSemitones(-Size.fifth, -7), -Interval.P5); + expect(Interval.fromSemitones(Size.fifth, 8), Interval.A5); + expect(Interval.fromSemitones(-Size.fifth, -8), -Interval.A5); + + expect(Interval.fromSemitones(Size.sixth, 8), Interval.m6); + expect(Interval.fromSemitones(-Size.sixth, -8), -Interval.m6); + expect(Interval.fromSemitones(Size.sixth, 9), Interval.M6); + expect(Interval.fromSemitones(-Size.sixth, -9), -Interval.M6); + + expect(Interval.fromSemitones(Size.seventh, 10), Interval.m7); + expect(Interval.fromSemitones(-Size.seventh, -10), -Interval.m7); + expect(Interval.fromSemitones(Size.seventh, 11), Interval.M7); + expect(Interval.fromSemitones(-Size.seventh, -11), -Interval.M7); + + expect(Interval.fromSemitones(Size.octave, 11), Interval.d8); + expect(Interval.fromSemitones(-Size.octave, -11), -Interval.d8); + expect(Interval.fromSemitones(Size.octave, 12), Interval.P8); + expect(Interval.fromSemitones(-Size.octave, -12), -Interval.P8); + expect(Interval.fromSemitones(Size.octave, 13), Interval.A8); + expect(Interval.fromSemitones(-Size.octave, -13), -Interval.A8); }); }); @@ -96,81 +88,45 @@ void main() { test('parses source as an Interval and return its value', () { expect( Interval.parse('AA4'), - const Interval.perfect(4, PerfectQuality.doublyAugmented), + const Interval.perfect(Size.fourth, PerfectQuality.doublyAugmented), ); expect(Interval.parse('A5'), Interval.A5); expect(Interval.parse('P1'), Interval.P1); expect( Interval.parse('P22'), - const Interval.perfect(22, PerfectQuality.perfect), + const Interval.perfect(Size(22), PerfectQuality.perfect), ); expect(Interval.parse('d5'), Interval.d5); expect( Interval.parse('dd8'), - const Interval.perfect(8, PerfectQuality.doublyDiminished), + const Interval.perfect(Size.octave, PerfectQuality.doublyDiminished), ); expect( Interval.parse('AA3'), - const Interval.imperfect(3, ImperfectQuality.doublyAugmented), + const Interval.imperfect( + Size.third, + ImperfectQuality.doublyAugmented, + ), ); expect(Interval.parse('A6'), Interval.A6); expect(Interval.parse('M3'), Interval.M3); expect( Interval.parse('M16'), - const Interval.imperfect(16, ImperfectQuality.major), + const Interval.imperfect(Size(16), ImperfectQuality.major), ); expect(Interval.parse('m2'), Interval.m2); expect(Interval.parse('d7'), Interval.d7); expect( Interval.parse('dd9'), - const Interval.imperfect(9, ImperfectQuality.doublyDiminished), + const Interval.imperfect( + Size.ninth, + ImperfectQuality.doublyDiminished, + ), ); }); }); - group('.sizeFromSemitones()', () { - test( - 'returns the Interval size corresponding to the given semitones', - () { - expect(Interval.sizeFromSemitones(-12), -8); - expect(Interval.sizeFromSemitones(-5), -4); - expect(Interval.sizeFromSemitones(-3), -3); - expect(Interval.sizeFromSemitones(-1), -2); - expect(Interval.sizeFromSemitones(0), 1); - expect(Interval.sizeFromSemitones(1), 2); - expect(Interval.sizeFromSemitones(3), 3); - expect(Interval.sizeFromSemitones(5), 4); - expect(Interval.sizeFromSemitones(7), 5); - expect(Interval.sizeFromSemitones(8), 6); - expect(Interval.sizeFromSemitones(10), 7); - expect(Interval.sizeFromSemitones(12), 8); - expect(Interval.sizeFromSemitones(13), 9); - expect(Interval.sizeFromSemitones(15), 10); - expect(Interval.sizeFromSemitones(17), 11); - expect(Interval.sizeFromSemitones(19), 12); - expect(Interval.sizeFromSemitones(20), 13); - expect(Interval.sizeFromSemitones(22), 14); - expect(Interval.sizeFromSemitones(24), 15); - expect(Interval.sizeFromSemitones(36), 22); - expect(Interval.sizeFromSemitones(48), 29); - }, - ); - - test( - 'returns null when no Interval size corresponds to the given semitones', - () { - expect(Interval.sizeFromSemitones(-4), isNull); - expect(Interval.sizeFromSemitones(-2), isNull); - expect(Interval.sizeFromSemitones(2), isNull); - expect(Interval.sizeFromSemitones(4), isNull); - expect(Interval.sizeFromSemitones(6), isNull); - expect(Interval.sizeFromSemitones(9), isNull); - expect(Interval.sizeFromSemitones(11), isNull); - }, - ); - }); - group('.semitones', () { test('returns the number of semitones of this Interval', () { expect(Interval.d1.semitones, -1); @@ -225,17 +181,17 @@ void main() { expect((-Interval.M13).semitones, -21); expect( - const Interval.perfect(15, PerfectQuality.perfect).semitones, + const Interval.perfect(Size(15), PerfectQuality.perfect).semitones, 24, ); expect( - const Interval.perfect(22, PerfectQuality.perfect).semitones, + const Interval.perfect(Size(22), PerfectQuality.perfect).semitones, 36, ); expect( - const Interval.perfect(29, PerfectQuality.perfect).semitones, + const Interval.perfect(Size(29), PerfectQuality.perfect).semitones, 48, ); }); @@ -248,7 +204,7 @@ void main() { expect(Interval.d1.isDescending, isFalse); expect(Interval.M9.isDescending, isFalse); expect( - const Interval.perfect(-4, PerfectQuality.doublyAugmented) + const Interval.perfect(Size(-4), PerfectQuality.doublyAugmented) .isDescending, isTrue, ); @@ -332,11 +288,11 @@ void main() { group('.simplified', () { test('returns the simplified of this Interval', () { expect( - const Interval.perfect(-22, PerfectQuality.perfect).simplified, + const Interval.perfect(Size(-22), PerfectQuality.perfect).simplified, -Interval.P8, ); expect( - const Interval.perfect(-15, PerfectQuality.perfect).simplified, + const Interval.perfect(Size(-15), PerfectQuality.perfect).simplified, -Interval.P8, ); expect((-Interval.M13).simplified, -Interval.M6); @@ -354,11 +310,11 @@ void main() { expect(Interval.P11.simplified, Interval.P4); expect(Interval.M13.simplified, Interval.M6); expect( - const Interval.perfect(15, PerfectQuality.perfect).simplified, + const Interval.perfect(Size(15), PerfectQuality.perfect).simplified, Interval.P8, ); expect( - const Interval.perfect(22, PerfectQuality.perfect).simplified, + const Interval.perfect(Size(22), PerfectQuality.perfect).simplified, Interval.P8, ); }); @@ -428,21 +384,21 @@ void main() { group('.respellBySize()', () { test('returns this Interval respelled by size', () { - expect(Interval.A4.respellBySize(5), Interval.d5); - expect(Interval.d5.respellBySize(4), Interval.A4); - expect(Interval.M2.respellBySize(3), Interval.d3); + expect(Interval.A4.respellBySize(Size.fifth), Interval.d5); + expect(Interval.d5.respellBySize(Size.fourth), Interval.A4); + expect(Interval.M2.respellBySize(Size.third), Interval.d3); expect( - Interval.M3.respellBySize(5), - const Interval.perfect(5, PerfectQuality.triplyDiminished), + Interval.M3.respellBySize(Size.fifth), + const Interval.perfect(Size.fifth, PerfectQuality.triplyDiminished), ); - expect(Interval.P1.respellBySize(2), Interval.d2); - expect(Interval.m2.respellBySize(1), Interval.A1); + expect(Interval.P1.respellBySize(Size.second), Interval.d2); + expect(Interval.m2.respellBySize(Size.unison), Interval.A1); - expect((-Interval.M3).respellBySize(-4), -Interval.d4); - expect((-Interval.d5).respellBySize(-4), -Interval.A4); + expect((-Interval.M3).respellBySize(-Size.fourth), -Interval.d4); + expect((-Interval.d5).respellBySize(-Size.fourth), -Interval.A4); expect( - (-Interval.P4).respellBySize(-5), - const Interval.perfect(-5, PerfectQuality.doublyDiminished), + (-Interval.P4).respellBySize(-Size.fifth), + const Interval.perfect(Size(-5), PerfectQuality.doublyDiminished), ); }); }); @@ -629,10 +585,10 @@ void main() { test('returns the negation of this Interval', () { expect( -Interval.M2, - const Interval.imperfect(-2, ImperfectQuality.major), + const Interval.imperfect(Size(-2), ImperfectQuality.major), ); expect( - -const Interval.imperfect(-6, ImperfectQuality.minor), + -const Interval.imperfect(Size(-6), ImperfectQuality.minor), Interval.m6, ); }); @@ -648,33 +604,36 @@ void main() { expect((-Interval.d8).toString(), 'desc d8'); expect(Interval.M9.toString(), 'M9 (M2)'); expect( - const Interval.imperfect(-10, ImperfectQuality.minor).toString(), + const Interval.imperfect(Size(-10), ImperfectQuality.minor) + .toString(), 'desc m10 (m3)', ); expect(Interval.A11.toString(), 'A11 (A4)'); expect( - const Interval.imperfect(-14, ImperfectQuality.major).toString(), + const Interval.imperfect(Size(-14), ImperfectQuality.major) + .toString(), 'desc M14 (M7)', ); expect( - const Interval.perfect(15, PerfectQuality.perfect).toString(), + const Interval.perfect(Size(15), PerfectQuality.perfect).toString(), 'P15 (P8)', ); expect( - const Interval.imperfect(-16, ImperfectQuality.diminished).toString(), + const Interval.imperfect(Size(-16), ImperfectQuality.diminished) + .toString(), 'desc d16 (d2)', ); expect( - const Interval.perfect(22, PerfectQuality.perfect).toString(), + const Interval.perfect(Size(22), PerfectQuality.perfect).toString(), 'P22 (P8)', ); expect( - const Interval.perfect(5, PerfectQuality(-4)).toString(), + const Interval.perfect(Size.fifth, PerfectQuality(-4)).toString(), 'dddd5', ); expect( - const Interval.imperfect(10, ImperfectQuality(6)).toString(), + const Interval.imperfect(Size.tenth, ImperfectQuality(6)).toString(), 'AAAAA10 (AAAAA3)', ); }); diff --git a/test/src/interval/size_test.dart b/test/src/interval/size_test.dart new file mode 100644 index 00000000..4d1fc188 --- /dev/null +++ b/test/src/interval/size_test.dart @@ -0,0 +1,48 @@ +import 'package:music_notes/music_notes.dart'; +import 'package:test/test.dart'; + +void main() { + group('Size', () { + group('constructor', () { + test('creates a different Size.unison ascending and descending', () { + expect(Size.unison, isNot(-Size.unison)); + }); + }); + + group('.fromSemitones()', () { + test('returns the Size corresponding to the given semitones', () { + expect(Size.fromSemitones(-12), -Size.octave); + expect(Size.fromSemitones(-5), -Size.fourth); + expect(Size.fromSemitones(-3), -Size.third); + expect(Size.fromSemitones(-1), -Size.second); + expect(Size.fromSemitones(0), Size.unison); + expect(Size.fromSemitones(1), Size.second); + expect(Size.fromSemitones(3), Size.third); + expect(Size.fromSemitones(5), Size.fourth); + expect(Size.fromSemitones(7), Size.fifth); + expect(Size.fromSemitones(8), Size.sixth); + expect(Size.fromSemitones(10), Size.seventh); + expect(Size.fromSemitones(12), Size.octave); + expect(Size.fromSemitones(13), Size.ninth); + expect(Size.fromSemitones(15), Size.tenth); + expect(Size.fromSemitones(17), Size.eleventh); + expect(Size.fromSemitones(19), Size.twelfth); + expect(Size.fromSemitones(20), Size.thirteenth); + expect(Size.fromSemitones(22), const Size(14)); + expect(Size.fromSemitones(24), const Size(15)); + expect(Size.fromSemitones(36), const Size(22)); + expect(Size.fromSemitones(48), const Size(29)); + }); + + test('returns null when no Size corresponds to the given semitones', () { + expect(Size.fromSemitones(-4), isNull); + expect(Size.fromSemitones(-2), isNull); + expect(Size.fromSemitones(2), isNull); + expect(Size.fromSemitones(4), isNull); + expect(Size.fromSemitones(6), isNull); + expect(Size.fromSemitones(9), isNull); + expect(Size.fromSemitones(11), isNull); + }); + }); + }); +} diff --git a/test/src/main.dart b/test/src/main.dart index e9433049..e73e6118 100644 --- a/test/src/main.dart +++ b/test/src/main.dart @@ -4,6 +4,7 @@ import 'harmony/harmonic_function_test.dart' as harmonic_function_test; import 'interval/interval_class_test.dart' as interval_class_test; import 'interval/interval_test.dart' as interval_test; import 'interval/quality_test.dart' as quality_test; +import 'interval/size_test.dart' as size_test; import 'key/key_signature_test.dart' as key_signature_test; import 'key/key_test.dart' as key_test; import 'key/mode_test.dart' as mode_test; @@ -31,6 +32,7 @@ void main() { interval_class_test.main(); interval_test.main(); quality_test.main(); + size_test.main(); accidental_test.main(); base_note_test.main(); closest_pitch_test.main(); diff --git a/test/src/note/base_note_test.dart b/test/src/note/base_note_test.dart index 2e30e766..cc1bb7af 100644 --- a/test/src/note/base_note_test.dart +++ b/test/src/note/base_note_test.dart @@ -79,26 +79,19 @@ void main() { }); group('.transposeBySize()', () { - test('throws an assertion error when size is zero', () { - expect( - () => BaseNote.c.transposeBySize(0), - throwsA(isA()), - ); - }); - test('transposes this BaseNote by Interval size', () { - expect(BaseNote.f.transposeBySize(-8), BaseNote.f); - expect(BaseNote.g.transposeBySize(-3), BaseNote.e); - expect(BaseNote.c.transposeBySize(-2), BaseNote.b); - expect(BaseNote.d.transposeBySize(-1), BaseNote.d); - expect(BaseNote.c.transposeBySize(1), BaseNote.c); - expect(BaseNote.d.transposeBySize(2), BaseNote.e); - expect(BaseNote.e.transposeBySize(3), BaseNote.g); - expect(BaseNote.e.transposeBySize(4), BaseNote.a); - expect(BaseNote.f.transposeBySize(5), BaseNote.c); - expect(BaseNote.a.transposeBySize(6), BaseNote.f); - expect(BaseNote.b.transposeBySize(7), BaseNote.a); - expect(BaseNote.c.transposeBySize(8), BaseNote.c); + expect(BaseNote.f.transposeBySize(-Size.octave), BaseNote.f); + expect(BaseNote.g.transposeBySize(-Size.third), BaseNote.e); + expect(BaseNote.c.transposeBySize(-Size.second), BaseNote.b); + expect(BaseNote.d.transposeBySize(-Size.unison), BaseNote.d); + expect(BaseNote.c.transposeBySize(Size.unison), BaseNote.c); + expect(BaseNote.d.transposeBySize(Size.second), BaseNote.e); + expect(BaseNote.e.transposeBySize(Size.third), BaseNote.g); + expect(BaseNote.e.transposeBySize(Size.fourth), BaseNote.a); + expect(BaseNote.f.transposeBySize(Size.fifth), BaseNote.c); + expect(BaseNote.a.transposeBySize(Size.sixth), BaseNote.f); + expect(BaseNote.b.transposeBySize(Size.seventh), BaseNote.a); + expect(BaseNote.c.transposeBySize(Size.octave), BaseNote.c); }); }); diff --git a/test/src/note/note_test.dart b/test/src/note/note_test.dart index 327999a9..25aa2333 100644 --- a/test/src/note/note_test.dart +++ b/test/src/note/note_test.dart @@ -611,20 +611,23 @@ void main() { expect(Note.c.transposeBy(Interval.M13), Note.a); expect( - Note.c - .transposeBy(const Interval.perfect(15, PerfectQuality.perfect)), + Note.c.transposeBy( + const Interval.perfect(Size(15), PerfectQuality.perfect), + ), Note.c, ); expect( - Note.c - .transposeBy(const Interval.perfect(22, PerfectQuality.perfect)), + Note.c.transposeBy( + const Interval.perfect(Size(22), PerfectQuality.perfect), + ), Note.c, ); expect( - Note.c - .transposeBy(const Interval.perfect(29, PerfectQuality.perfect)), + Note.c.transposeBy( + const Interval.perfect(Size(29), PerfectQuality.perfect), + ), Note.c, ); }); diff --git a/test/src/note/pitch_test.dart b/test/src/note/pitch_test.dart index e3995f2f..d08286bd 100644 --- a/test/src/note/pitch_test.dart +++ b/test/src/note/pitch_test.dart @@ -612,23 +612,23 @@ void main() { expect(Note.c.inOctave(3).interval(Note.c.inOctave(4)), Interval.P8); expect( Note.c.inOctave(3).interval(Note.c.inOctave(5)), - const Interval.perfect(15, PerfectQuality.perfect), + const Interval.perfect(Size(15), PerfectQuality.perfect), ); expect( Note.c.inOctave(3).interval(Note.c.inOctave(6)), - const Interval.perfect(22, PerfectQuality.perfect), + const Interval.perfect(Size(22), PerfectQuality.perfect), ); expect( Note.c.inOctave(2).interval(Note.c.inOctave(6)), - const Interval.perfect(29, PerfectQuality.perfect), + const Interval.perfect(Size(29), PerfectQuality.perfect), ); expect( skip: true, () => Note.c.inOctave(4).interval(Note.b.sharp.inOctave(3)), - const Interval.perfect(29, PerfectQuality.perfect), + const Interval.perfect(Size(29), PerfectQuality.perfect), ); }); }); @@ -886,23 +886,23 @@ void main() { ); expect( - Note.c - .inOctave(4) - .transposeBy(const Interval.perfect(15, PerfectQuality.perfect)), + Note.c.inOctave(4).transposeBy( + const Interval.perfect(Size(15), PerfectQuality.perfect), + ), Note.c.inOctave(6), ); expect( - Note.c - .inOctave(4) - .transposeBy(const Interval.perfect(22, PerfectQuality.perfect)), + Note.c.inOctave(4).transposeBy( + const Interval.perfect(Size(22), PerfectQuality.perfect), + ), Note.c.inOctave(7), ); expect( - Note.c - .inOctave(4) - .transposeBy(const Interval.perfect(29, PerfectQuality.perfect)), + Note.c.inOctave(4).transposeBy( + const Interval.perfect(Size(29), PerfectQuality.perfect), + ), Note.c.inOctave(8), ); });