diff --git a/.vscode/settings.json b/.vscode/settings.json index 5f6b3be1..ed8f7ad3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,7 +9,8 @@ "lydian", "mixolydian", "octatonic", - "phrygian" + "phrygian", + "sublist" ], "editor.unicodeHighlight.allowedCharacters": { "′": true diff --git a/lib/music_notes.dart b/lib/music_notes.dart index 8f10c326..e7f00176 100644 --- a/lib/music_notes.dart +++ b/lib/music_notes.dart @@ -12,6 +12,8 @@ import 'utils/iterable.dart'; import 'utils/num_extension.dart'; part 'src/enharmonic.dart'; +part 'src/harmony/chord.dart'; +part 'src/harmony/chord_pattern.dart'; part 'src/interval/enharmonic_interval.dart'; part 'src/interval/interval.dart'; part 'src/interval/interval_size_extension.dart'; diff --git a/lib/src/harmony/chord.dart b/lib/src/harmony/chord.dart new file mode 100644 index 00000000..86107c18 --- /dev/null +++ b/lib/src/harmony/chord.dart @@ -0,0 +1,35 @@ +part of '../../music_notes.dart'; + +class Chord> { + /// The [Scalable] items this [Chord] is built of. + final List items; + + /// Creates a new [Chord] from [items]. + const Chord(this.items); + + /// The root [Scalable] of this [Chord]. + T get root => items.first; + + /// Returns the [ChordPattern] for this [Chord]. + /// + /// Example: + /// ```dart + /// const Chord([Note.a, Note.c, Note.e]).pattern == ChordPattern.minorTriad + /// const Chord([Note.g, Note.b, Note.d, Note.f]).pattern + /// == ChordPattern.majorTriad.add7() + /// ``` + ChordPattern get pattern => ChordPattern([ + // We skip the root of the chord. + for (final scalable in items.skip(1)) root.interval(scalable), + ]); + + @override + String toString() => '$root ${pattern.abbreviation} (${items.join(' ')})'; + + @override + bool operator ==(Object other) => + other is Chord && ListEquality().equals(items, other.items); + + @override + int get hashCode => Object.hashAll(items); +} diff --git a/lib/src/harmony/chord_pattern.dart b/lib/src/harmony/chord_pattern.dart new file mode 100644 index 00000000..1201e4e9 --- /dev/null +++ b/lib/src/harmony/chord_pattern.dart @@ -0,0 +1,130 @@ +part of '../../music_notes.dart'; + +class ChordPattern { + /// The intervals from the root note. + final List intervals; + + /// Creates a new [ChordPattern] from [intervals]. + const ChordPattern(this.intervals); + + static const augmentedTriad = ChordPattern([ + Interval.majorThird, + Interval.augmentedFifth, + ]); + + static const majorTriad = ChordPattern([ + Interval.majorThird, + Interval.perfectFifth, + ]); + + static const minorTriad = ChordPattern([ + Interval.minorThird, + Interval.perfectFifth, + ]); + + static const diminishedTriad = ChordPattern([ + Interval.minorThird, + Interval.diminishedFifth, + ]); + + /// Returns the [Chord] from [scalable]. + /// + /// Example: + /// ```dart + /// ChordPattern.majorTriad.from(Note.c) + /// == const Chord([Note.c, Note.e, Note.g]) + /// ``` + Chord from>(T scalable) => Chord( + intervals.fold( + [scalable], + (chordItems, interval) => + [...chordItems, scalable.transposeBy(interval)], + ), + ); + + /// Returns the root triad of this [ChordPattern]. + /// + /// Example: + /// ```dart + /// ChordPattern.majorTriad.add7().add9().rootTriad == ChordPattern.majorTriad + /// ``` + ChordPattern get rootTriad => ChordPattern(intervals.sublist(0, 2)); + + /// Whether this [ChordPattern] is [ImperfectQuality.augmented]. + bool get isAugmented => rootTriad == augmentedTriad; + + /// Whether this [ChordPattern] is [ImperfectQuality.major]. + bool get isMajor => rootTriad == majorTriad; + + /// Whether this [ChordPattern] is [ImperfectQuality.minor]. + bool get isMinor => rootTriad == minorTriad; + + /// Whether this [ChordPattern] is [ImperfectQuality.diminished]. + bool get isDiminished => rootTriad == diminishedTriad; + + /// 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]. + ChordPattern add(Interval interval, {List? replaceSizes}) { + final sizesToReplace = [interval.size, ...?replaceSizes]; + final filteredIntervals = intervals.whereNot( + (chordInterval) => sizesToReplace.contains(chordInterval.size), + ); + + return ChordPattern( + // Keep the intervals sorted after these operations. + SplayTreeSet.of([...filteredIntervals, interval]).toList(), + ); + } + + /// Returns the abbreviated quality representing this [ChordPattern]. + /// + /// Example: + /// ```dart + /// ChordPattern.majorTriad.abbreviation == 'maj.' + /// ChordPattern.diminishedTriad.abbreviation == 'dim.' + /// ``` + String get abbreviation => switch (this) { + _ when isAugmented => 'aug.', + _ when isMajor => 'maj.', + _ when isMinor => 'min.', + _ when isDiminished => 'dim.', + _ => '?', + }; + + @override + String toString() => '$abbreviation (${intervals.join(' ')})'; + + @override + bool operator ==(Object other) => + other is ChordPattern && + const ListEquality().equals(intervals, other.intervals); + + @override + int get hashCode => Object.hashAll(intervals); +} diff --git a/lib/src/interval/interval.dart b/lib/src/interval/interval.dart index 11560846..dc13254c 100644 --- a/lib/src/interval/interval.dart +++ b/lib/src/interval/interval.dart @@ -63,6 +63,18 @@ final class Interval implements Comparable { static const perfectOctave = Interval.perfect(8, PerfectQuality.perfect); static const augmentedOctave = Interval.perfect(8, PerfectQuality.augmented); + static const minorNinth = Interval.imperfect(9, ImperfectQuality.minor); + static const majorNinth = Interval.imperfect(9, ImperfectQuality.major); + + static const diminishedEleventh = + Interval.perfect(11, PerfectQuality.diminished); + static const perfectEleventh = Interval.perfect(11, PerfectQuality.perfect); + static const augmentedEleventh = + Interval.perfect(11, PerfectQuality.augmented); + + static const minorThirteenth = Interval.imperfect(13, ImperfectQuality.minor); + static const majorThirteenth = Interval.imperfect(13, ImperfectQuality.major); + /// 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'), diff --git a/test/src/harmony/chord_pattern_test.dart b/test/src/harmony/chord_pattern_test.dart new file mode 100644 index 00000000..ae58cc81 --- /dev/null +++ b/test/src/harmony/chord_pattern_test.dart @@ -0,0 +1,432 @@ +import 'package:music_notes/music_notes.dart'; +import 'package:test/test.dart'; + +void main() { + group('ChordPattern', () { + group('.from()', () { + test('should return the Chord from this ChordPattern', () { + expect( + ChordPattern.majorTriad.from(Note.e), + Chord([Note.e, Note.g.sharp, Note.b]), + ); + expect( + ChordPattern.minorTriad.add7().from(Note.f), + Chord([Note.f, Note.a.flat, Note.c, Note.e.flat]), + ); + expect( + ChordPattern.diminishedTriad + .add7(ImperfectQuality.diminished) + .from(Note.b.flat.inOctave(4)), + Chord([ + Note.b.flat.inOctave(4), + Note.d.flat.inOctave(5), + Note.f.flat.inOctave(5), + Note.a.flat.flat.inOctave(5), + ]), + ); + }); + }); + + group('.rootTriad', () { + test('should return the root triad of this ChordPattern', () { + expect( + const ChordPattern([ + Interval.majorThird, + Interval.augmentedFifth, + Interval.majorSeventh, + ]).rootTriad, + ChordPattern.augmentedTriad, + ); + expect(ChordPattern.majorTriad.rootTriad, ChordPattern.majorTriad); + expect( + ChordPattern.minorTriad.add7().add9().rootTriad, + ChordPattern.minorTriad, + ); + expect( + const ChordPattern([ + Interval.minorThird, + Interval.diminishedFifth, + Interval.diminishedSeventh, + ]).rootTriad, + ChordPattern.diminishedTriad, + ); + }); + }); + + group('.isAugmented', () { + test('should return whether this ChordPattern is augmented', () { + expect(ChordPattern.augmentedTriad.isAugmented, isTrue); + expect(ChordPattern.majorTriad.isAugmented, isFalse); + expect(ChordPattern.minorTriad.isAugmented, isFalse); + expect(ChordPattern.diminishedTriad.isAugmented, isFalse); + + expect(ChordPattern.augmentedTriad.add7().add9().isAugmented, isTrue); + }); + }); + + group('.isMajor', () { + test('should return whether this ChordPattern is major', () { + expect(ChordPattern.augmentedTriad.isMajor, isFalse); + expect(ChordPattern.majorTriad.isMajor, isTrue); + expect(ChordPattern.minorTriad.isMajor, isFalse); + expect(ChordPattern.diminishedTriad.isMajor, isFalse); + + expect(ChordPattern.majorTriad.add7().add9().isMajor, isTrue); + }); + }); + + group('.isMinor', () { + test('should return whether this ChordPattern is minor', () { + expect(ChordPattern.augmentedTriad.isMinor, isFalse); + expect(ChordPattern.majorTriad.isMinor, isFalse); + expect(ChordPattern.minorTriad.isMinor, isTrue); + expect(ChordPattern.diminishedTriad.isMinor, isFalse); + + expect(ChordPattern.minorTriad.add7().add9().isMinor, isTrue); + }); + }); + + group('.isDiminished', () { + test('should return whether this ChordPattern is diminished', () { + expect(ChordPattern.augmentedTriad.isDiminished, isFalse); + expect(ChordPattern.majorTriad.isDiminished, isFalse); + expect(ChordPattern.minorTriad.isDiminished, isFalse); + expect(ChordPattern.diminishedTriad.isDiminished, isTrue); + + expect(ChordPattern.diminishedTriad.add7().add9().isDiminished, isTrue); + }); + }); + + group('.sus2()', () { + test('should turn this ChordPattern into a suspended 2nd chord', () { + expect( + ChordPattern.majorTriad.sus2(), + const ChordPattern([Interval.majorSecond, Interval.perfectFifth]), + ); + expect( + ChordPattern.minorTriad.sus4().sus2(), + const ChordPattern([Interval.majorSecond, Interval.perfectFifth]), + ); + expect( + ChordPattern.majorTriad.sus2().sus2(), + const ChordPattern([Interval.majorSecond, Interval.perfectFifth]), + ); + expect( + ChordPattern.minorTriad.add7().sus2(), + const ChordPattern([ + Interval.majorSecond, + Interval.perfectFifth, + Interval.minorSeventh, + ]), + ); + }); + }); + + group('.sus4()', () { + test('should turn this ChordPattern into a suspended 4th chord', () { + expect( + ChordPattern.majorTriad.sus4(), + const ChordPattern([Interval.perfectFourth, Interval.perfectFifth]), + ); + expect( + ChordPattern.minorTriad.sus2().sus4(), + const ChordPattern([Interval.perfectFourth, Interval.perfectFifth]), + ); + expect( + ChordPattern.majorTriad.sus4().sus4(), + const ChordPattern([Interval.perfectFourth, Interval.perfectFifth]), + ); + expect( + ChordPattern.minorTriad.add7().sus4(), + const ChordPattern([ + Interval.perfectFourth, + Interval.perfectFifth, + Interval.minorSeventh, + ]), + ); + }); + }); + + group('.add6()', () { + test('should add a 6th Interval to this ChordPattern', () { + expect( + ChordPattern.majorTriad.add6(), + const ChordPattern([ + Interval.majorThird, + Interval.perfectFifth, + Interval.majorSixth, + ]), + ); + expect( + ChordPattern.minorTriad.sus2().add6(), + const ChordPattern([ + Interval.majorSecond, + Interval.perfectFifth, + Interval.majorSixth, + ]), + ); + expect( + ChordPattern.majorTriad.sus2().add6(ImperfectQuality.minor), + const ChordPattern([ + Interval.majorSecond, + Interval.perfectFifth, + Interval.minorSixth, + ]), + ); + expect( + ChordPattern.minorTriad.add6(ImperfectQuality.minor).add9(), + const ChordPattern([ + Interval.minorThird, + Interval.perfectFifth, + Interval.minorSixth, + Interval.majorNinth, + ]), + ); + }); + }); + + group('.add7()', () { + test('should add a 7th Interval to this ChordPattern', () { + expect( + ChordPattern.majorTriad.add7(), + const ChordPattern([ + Interval.majorThird, + Interval.perfectFifth, + Interval.minorSeventh, + ]), + ); + expect( + ChordPattern.minorTriad.sus2().add7(), + const ChordPattern([ + Interval.majorSecond, + Interval.perfectFifth, + Interval.minorSeventh, + ]), + ); + expect( + ChordPattern.majorTriad.sus2().add7(ImperfectQuality.major), + const ChordPattern([ + Interval.majorSecond, + Interval.perfectFifth, + Interval.majorSeventh, + ]), + ); + expect( + ChordPattern.minorTriad.add7(ImperfectQuality.major), + const ChordPattern([ + Interval.minorThird, + Interval.perfectFifth, + Interval.majorSeventh, + ]), + ); + }); + }); + + group('.add9()', () { + test('should add a 9th Interval to this ChordPattern', () { + expect( + ChordPattern.majorTriad.add9(), + const ChordPattern([ + Interval.majorThird, + Interval.perfectFifth, + Interval.majorNinth, + ]), + ); + expect( + ChordPattern.minorTriad.sus4().add9(), + const ChordPattern([ + Interval.perfectFourth, + Interval.perfectFifth, + Interval.majorNinth, + ]), + ); + expect( + ChordPattern.majorTriad.sus2().add9(ImperfectQuality.minor), + const ChordPattern([ + Interval.majorSecond, + Interval.perfectFifth, + Interval.minorNinth, + ]), + ); + expect( + ChordPattern.minorTriad.add9(ImperfectQuality.minor), + const ChordPattern([ + Interval.minorThird, + Interval.perfectFifth, + Interval.minorNinth, + ]), + ); + }); + }); + + group('.add11()', () { + test('should add an 11th Interval to this ChordPattern', () { + expect( + ChordPattern.majorTriad.add11(), + const ChordPattern([ + Interval.majorThird, + Interval.perfectFifth, + Interval.perfectEleventh, + ]), + ); + expect( + ChordPattern.minorTriad.add7().add9().add11(), + const ChordPattern([ + Interval.minorThird, + Interval.perfectFifth, + Interval.minorSeventh, + Interval.majorNinth, + Interval.perfectEleventh, + ]), + ); + expect( + ChordPattern.majorTriad + .sus2() + .add9(ImperfectQuality.minor) + .add11(PerfectQuality.diminished), + const ChordPattern([ + Interval.majorSecond, + Interval.perfectFifth, + Interval.minorNinth, + Interval.diminishedEleventh, + ]), + ); + expect( + ChordPattern.minorTriad.add11(PerfectQuality.augmented), + const ChordPattern([ + Interval.minorThird, + Interval.perfectFifth, + Interval.augmentedEleventh, + ]), + ); + }); + }); + + group('.add13()', () { + test('should add an 13th Interval to this ChordPattern', () { + expect( + ChordPattern.majorTriad.add13(), + const ChordPattern([ + Interval.majorThird, + Interval.perfectFifth, + Interval.majorThirteenth, + ]), + ); + expect( + ChordPattern.minorTriad.add7().add9().add11().add13(), + const ChordPattern([ + Interval.minorThird, + Interval.perfectFifth, + Interval.minorSeventh, + Interval.majorNinth, + Interval.perfectEleventh, + Interval.majorThirteenth, + ]), + ); + expect( + ChordPattern.majorTriad + .add9(ImperfectQuality.minor) + .add11(PerfectQuality.augmented) + .sus2() + .add13(ImperfectQuality.minor), + const ChordPattern([ + Interval.majorSecond, + Interval.perfectFifth, + Interval.minorNinth, + Interval.augmentedEleventh, + Interval.minorThirteenth, + ]), + ); + expect( + ChordPattern.minorTriad.add13(ImperfectQuality.minor), + const ChordPattern([ + Interval.minorThird, + Interval.perfectFifth, + Interval.minorThirteenth, + ]), + ); + }); + }); + + group('.add()', () { + test('should add an Interval to this ChordPattern', () { + expect( + ChordPattern.majorTriad.add(Interval.majorSeventh), + const ChordPattern([ + Interval.majorThird, + Interval.perfectFifth, + Interval.majorSeventh, + ]), + ); + }); + + test('should ignore any previous Interval size in this ChordPattern', () { + expect( + const ChordPattern([ + Interval.minorThird, + Interval.perfectFifth, + Interval.majorSeventh, + ]).add(Interval.majorSeventh), + const ChordPattern([ + Interval.minorThird, + Interval.perfectFifth, + Interval.majorSeventh, + ]), + ); + expect( + const ChordPattern([ + Interval.minorThird, + Interval.perfectFifth, + Interval.majorSeventh, + ]).add(Interval.minorSeventh), + const ChordPattern([ + Interval.minorThird, + Interval.perfectFifth, + Interval.minorSeventh, + ]), + ); + }); + }); + + group('.toString()', () { + test( + 'should return the string representation of this ChordPattern', + () { + expect(ChordPattern.augmentedTriad.toString(), 'aug. (M3 A5)'); + expect(ChordPattern.majorTriad.toString(), 'maj. (M3 P5)'); + expect(ChordPattern.minorTriad.toString(), 'min. (m3 P5)'); + expect(ChordPattern.diminishedTriad.toString(), 'dim. (m3 d5)'); + + expect( + ChordPattern.augmentedTriad.add7().toString(), + 'aug. (M3 A5 m7)', + ); + expect(ChordPattern.majorTriad.add7().toString(), 'maj. (M3 P5 m7)'); + expect(ChordPattern.minorTriad.add7().toString(), 'min. (m3 P5 m7)'); + expect( + ChordPattern.diminishedTriad.add7().toString(), + 'dim. (m3 d5 m7)', + ); + }, + ); + }); + + group('.hashCode', () { + test('should ignore equal ChordPattern instances in a Set', () { + final collection = { + ChordPattern.augmentedTriad, + ChordPattern.majorTriad, + ChordPattern.minorTriad, + ChordPattern.diminishedTriad, + }; + collection.addAll(collection); + expect(collection.toList(), [ + ChordPattern.augmentedTriad, + ChordPattern.majorTriad, + ChordPattern.minorTriad, + ChordPattern.diminishedTriad, + ]); + }); + }); + }); +} diff --git a/test/src/harmony/chord_test.dart b/test/src/harmony/chord_test.dart new file mode 100644 index 00000000..0d41b649 --- /dev/null +++ b/test/src/harmony/chord_test.dart @@ -0,0 +1,75 @@ +import 'package:music_notes/music_notes.dart'; +import 'package:test/test.dart'; + +void main() { + group('Chord', () { + group('.root', () { + test('should return the root of this Chord', () { + expect(ChordPattern.majorTriad.from(Note.f).root, Note.f); + expect( + Chord([Note.d.inOctave(3), Note.f.inOctave(3), Note.a.inOctave(3)]) + .root, + Note.d.inOctave(3), + ); + }); + }); + + group('.pattern', () { + test('should return the ChordPattern for this Chord', () { + expect( + ChordPattern.majorTriad.from(Note.c).pattern, + ChordPattern.majorTriad, + ); + expect( + const Chord([Note.a, Note.c, Note.e, Note.g]).pattern, + ChordPattern.minorTriad.add7(), + ); + expect( + Chord([ + Note.a.flat.inOctave(4), + Note.c.inOctave(5), + Note.e.inOctave(5), + Note.g.inOctave(5), + ]).pattern, + ChordPattern.augmentedTriad.add7(ImperfectQuality.major), + ); + }); + }); + + group('.toString()', () { + test('should return the string representation of this Chord', () { + expect( + ChordPattern.majorTriad.from(Note.d).toString(), + 'D maj. (D F♯ A)', + ); + expect( + Chord([Note.g.sharp, Note.b, Note.d.sharp]).toString(), + 'G♯ min. (G♯ B D♯)', + ); + expect( + ChordPattern.augmentedTriad + .add7() + .from(Note.c.inOctave(3)) + .toString(), + 'C3 aug. (C3 E3 G♯3 B♭3)', + ); + }); + }); + + group('.hashCode', () { + test('should ignore equal Chord instances in a Set', () { + final collection = { + const Chord([Note.c, Note.e, Note.g]), + ChordPattern.minorTriad.from(Note.g), + ChordPattern.augmentedTriad.from(Note.d), + }; + collection.addAll(collection); + expect(collection.toList(), [ + const Chord([Note.c, Note.e, Note.g]), + ChordPattern.minorTriad.from(Note.g), + ChordPattern.augmentedTriad.from(Note.d), + ]); + }); + }); + }); +} diff --git a/test/src/interval/enharmonic_interval_test.dart b/test/src/interval/enharmonic_interval_test.dart index 561e7b44..0bc0bc68 100644 --- a/test/src/interval/enharmonic_interval_test.dart +++ b/test/src/interval/enharmonic_interval_test.dart @@ -131,12 +131,12 @@ void main() { }); expect(const EnharmonicInterval(13).spellings, { Interval.augmentedOctave, - const Interval.imperfect(9, ImperfectQuality.minor), + Interval.minorNinth, const Interval.imperfect(10, ImperfectQuality.doublyDiminished), }); expect(const EnharmonicInterval(-13).spellings, { -Interval.augmentedOctave, - -const Interval.imperfect(9, ImperfectQuality.minor), + -Interval.minorNinth, -const Interval.imperfect(10, ImperfectQuality.doublyDiminished), }); }, diff --git a/test/src/interval/interval_test.dart b/test/src/interval/interval_test.dart index 4e4b1d22..a6f62ea5 100644 --- a/test/src/interval/interval_test.dart +++ b/test/src/interval/interval_test.dart @@ -151,10 +151,7 @@ void main() { expect(Interval.minorThird.isDescending, isFalse); expect((-Interval.perfectFifth).isDescending, isTrue); expect(Interval.diminishedUnison.isDescending, isFalse); - expect( - const Interval.imperfect(9, ImperfectQuality.major).isDescending, - isFalse, - ); + expect(Interval.majorNinth.isDescending, isFalse); expect( const Interval.perfect(-4, PerfectQuality.doublyAugmented) .isDescending, @@ -301,18 +298,12 @@ void main() { expect((-Interval.perfectFifth).toString(), 'desc P5'); expect(Interval.diminishedSeventh.toString(), 'd7'); expect((-Interval.diminishedOctave).toString(), 'desc d8'); - expect( - const Interval.imperfect(9, ImperfectQuality.major).toString(), - 'M9 (M2)', - ); + expect(Interval.majorNinth.toString(), 'M9 (M2)'); expect( const Interval.imperfect(-10, ImperfectQuality.minor).toString(), 'desc m10 (m3)', ); - expect( - const Interval.perfect(11, PerfectQuality.augmented).toString(), - 'A11 (A4)', - ); + expect(Interval.augmentedEleventh.toString(), 'A11 (A4)'); expect( const Interval.imperfect(-14, ImperfectQuality.major).toString(), 'desc M14 (M7)', diff --git a/test/src/main.dart b/test/src/main.dart index 276c691c..e44b0b56 100644 --- a/test/src/main.dart +++ b/test/src/main.dart @@ -1,3 +1,5 @@ +import 'harmony/chord_pattern_test.dart' as chord_pattern_test; +import 'harmony/chord_test.dart' as chord_test; import 'interval/enharmonic_interval_test.dart' as enharmonic_interval_test; import 'interval/interval_size_extension_test.dart' as interval_size_extension_test; @@ -17,6 +19,8 @@ import 'tonality/mode_test.dart' as mode_test; import 'tonality/tonality_test.dart' as tonality_test; void main() { + chord_pattern_test.main(); + chord_test.main(); enharmonic_interval_test.main(); interval_size_extension_test.main(); interval_test.main();